python - 进程与线程 -1
相关知识
并发与并行:
- 并发: 只有一个CPU,一个CPU执行多个任务,把CPU运行时间分成若干时间段,每段执行不同的任务。并发解决了程序排队的问题,如果一个程序发生阻塞,其他程序仍然正常执行。
- 并行:就是多个CPU执行任务,一个CPU执行一个,互不干涉。
进程:
进程就是一个程序(运行的实例),每个进程都有自己的地址空间。
线程:
线程是进程的一部分,只有一个线程的进程叫做单线程,在一个进程中,线程共享堆,同时每个线程有自己的栈。
- 进程与进程:程序和程序之间的关系
- 线程与线程:程序和程序内的小任务
- 进程有自己的资源空间,一个进程包含若干线程,多个线程共享同一进程内的资源。
- 线程调度与切换比进程快很多。
CPU密集型代码(循环,计算等)使用多进程
IO密集型代码(文件处理,网络爬虫等)使用多线程
阻塞和非阻塞:
- 阻塞就是线程或进程被挂起不执行。
- 非阻塞就是没有被挂起,继续执行。比如:一个请求要很长时间,不管这个请求,继续向下执行,当请求结束后系统会通知进程处理。
同步和异步:
同步就是阻塞,异步就是非阻塞
- 同步:就是一个进程在执行某个请求时候,如果该请求要等很长时间才能返回,那进程就会一直等待下去。
- 异步:就是进程继续向下执行,不管这个请求的处理。当请求结束后系统会通知进程处理。
- 同步调用:调用方主动查询被调用方叫做同步调用
- 异步调用:被调用方主动告诉调用方叫做异步调用
协程:
又称微线程,协程看上去是子程序,但在执行过程中在子程序内部可以中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。有点类似CPU的中断。
- 协程有点像多线程,但执行效率高。因为是子程序切换不是线程切换(没有线程切换的开销)
- 协程是一个线程在执行,存在于一个线程内。
Python对协程的支持是通过generator实现的。
在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。
但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。
def fun1():
r = "null"
while True:
# yield 中断函数执行,返回r
# value 接收调用者发来的数据
value = yield r
if not value:
return
print(f"传递过来的值是:{value}")
r = "fun1_value"
def fun2(c):
# 相当于next()
# 恢复执行,并向生成器函数发送一个值。send()会返回生成器所产生的下一个值。如果没有下一个值就会发生StopIteration异常
# 第一次调用要发送一个None值,
c.send(None)
print("ok")
for i in range(1,5):
r = c.send(i)
print(f"生成器返回的结果:{r}")
c.close()
f1 = fun1()
fun2(f1)
1. 使用multiprocessing模块创建进程
1.1 用Process类创建进程
在这个模块中有一个Process类代表一个进程对象:
Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
- group: 参数未使用,值始终为None
- target: 表示当前进程启动时执行的可调用对象
- name: 进程 实例的别名
- args: 传递给target函数的参数(元组)
- kwargs:传递给target函数的参数(字典)
- daemon:
from multiprocessing import Process
def test(*args):
print("我是子进程:")
for i in args:
print(i, end=', ')
def main():
print("主进程开始")
# 创建进程对象
p = Process(target=test, args=(1, "abc", 2, 3, 4))
# 启动子进程
p.start()
print("主进程结束")
if __name__ == '__main__':
main()
输出:
主进程开始
主进程结束
我是子进程:
1, abc, 2, 3, 4,
Process的实例方法:
方法 | 说明 |
---|---|
is_alive() | 判断进程实例是否还在执行(True, False) |
start() | 启动进程实例(创建子进程) |
join([timeout]) | 等待进程实例执行结束,或等待多少秒 |
terminate() | 不管任务是否完成,立即终止 |
run() | 如果没有给定target参数,执行start时,就执行对象中的run方法 |
属性 | |
name | 进程别名 |
pid | 进程PID值 |
import os
from multiprocessing import Process
from time import sleep
def test(args):
print(f"我是子进程:{os.getpid()}")
sleep(args)
print(f"子进程:{os.getpid()}结束")
def main():
print("主进程开始")
# 创建进程对象
p1 = Process(target=test, args=(1,))
p2 = Process(target=test, args=(3,))
# 启动子进程
p1.start()
p2.start()
print(f"p1.pid:{p1.pid}, p2.pid:{p2.pid}")
p1.join()
p2.join()
print("主进程结束")
if __name__ == '__main__':
main()
1.2 用Process子类创建进程
- 定义子类,继承Process
- 重写Process中的run方法
from multiprocessing import Process
from time import sleep
class SubProcess(Process):
def __init__(self, time):
Process.__init__(self)
self.time = time
# 重写run方法,调用父类的start()时会自动执行run()
def run(self):
print(f"子进程{self.name}开始执行")
sleep(self.time)
print(f"子进程{self.name}执行结束")
def main():
print("主进程开始")
p1 = SubProcess(2)
p2 = SubProcess(1)
p1.start()
p2.start()
p1.join()
p2.join()
print("主进程结束")
if __name__ == "__main__":
main()
1.3 用Pool进程池创建进程
进程池可指定大小,之后添加N个任务,进程池自动管理进程的运行。
Pool类的方法:
方法 | 说明 |
---|---|
apply_async( func, args=(), kwds={}, callback=None, error_callback=None) | 非阻塞方式调用func,并行执行任务 |
apply(func, args=(), kwds={}) | 阻塞方式调用func,需要等上一个进程结束才能执行下一个进程。(不并行了) |
close() | 关闭Pool,不再接受新的任务 |
terminate() | 不管任务是否完成,立即终止 |
join() | 等待全部进程完成,必须在close, terminate之后使用 |
import os
import random
from multiprocessing import Pool
from time import sleep
def task(time):
print(f"进程{os.getpid()}开始执行, time:{time}")
sleep(time)
print(f"\t进程{os.getpid()}执行结束")
def main():
print("主进程开始")
p = Pool(3)
# 添加10个任务
for i in range(10):
p.apply_async(func=task, args=(random.randint(1, 5),))
p.close()
p.join()
print("主进程结束")
if __name__ == "__main__":
main()
执行结果:
主进程开始
进程3704开始执行, time:3
进程10648开始执行, time:3
进程10580开始执行, time:1
进程10580执行结束
进程10580开始执行, time:3
进程10648执行结束
进程3704执行结束
进程10648开始执行, time:1
进程3704开始执行, time:4
进程10648执行结束
进程10580执行结束
进程10648开始执行, time:5
进程10580开始执行, time:2
进程10580执行结束
进程10580开始执行, time:3
进程3704执行结束
进程3704开始执行, time:4
进程10648执行结束
进程10580执行结束
进程3704执行结束
主进程结束
可以看出,同时执行3个进程,完成一个后,另一个进程再开始执行。好比:有三个水龙头放水,要接10盆水一样,最多三个进程同时工作。
2. 进程间通信
每个进程都有自己的地址空间,内存,数据栈及其它记录其运行状态的辅助数据。
# 全局
global_value = 100
def add(time):
global global_value
print(f"进程{os.getpid()}开始运行,值为:{global_value}")
global_value += 100
sleep(time)
print(f"进程{os.getpid()}运行结束, 值为:{global_value}")
def sub(time):
global global_value
print(f"进程{os.getpid()}开始运行,值为:{global_value}")
global_value -= 100
sleep(time)
print(f"进程{os.getpid()}运行结束, 值为:{global_value}")
def main():
print("主进程开始")
p1 = Process(target=add, args=(1,))
p2 = Process(target=sub, args=(2,))
p1.start()
p2.start()
p1.join()
p2.join()
global global_value
print("global:", global_value)
print("主进程结束")
if __name__ == "__main__":
main()
结果:
主进程开始
进程10320开始运行,值为:100
进程10320运行结束, 值为:200
进程7300开始运行,值为:100
进程7300运行结束, 值为:0
global: 100
主进程结束
可以看出global全局变量并没有在进程之间数据共享。
2.1 通过队列实现进程间的通信
使用multiprocessing模块中的Queue实现进程之间的数据传递。
q = Queue(num)
创建一个Queue对列对象
方法 | 说明 |
---|---|
q.qsize() | 当前队列包含的消息数量 |
q.empty() | 队列为空返回True |
q.full() | 队列满了返回True |
q.get(block, timeout) | 从队列中获取一条消息,然后将其从队列中移除 |
q.get_nowait() | 队列为空(读不到数据)抛出异常 |
q.put(item,block,timeout) | 将item写入队列,block默认为True |
q.put_nowait(item) | 队列满了(存不了数据)抛出异常 |
- block默认为True。如果block为True,而且没有指定timeout(秒),消息队列将被阻塞,直到能读取或写入消息为止。如果设置了timeout,时间到了还没读到消息或写入消息,则抛出Queue.Empty异常
- block为False,如果消息队列满或空,即不能写入或读取直接抛出异常。
from multiprocessing import Process, Queue
from time import sleep
def write(q: Queue):
for i in range(10):
message = f"{i}"
q.put(message)
print(f"写入消息:{message}")
q.put("end")
def read(q: Queue):
message = ""
while message != "end":
sleep(1)
message = q.get()
print(f"\t读取消息:{message}")
def main():
print("主进程开始")
q = Queue(2)
p1 = Process(target=write, args=(q,))
p2 = Process(target=read, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
print("主进程结束")
结果:
主进程开始
写入消息:0
写入消息:1
读取消息:0
写入消息:2
读取消息:1
写入消息:3
读取消息:2
写入消息:4
读取消息:3
写入消息:5
读取消息:4
写入消息:6
读取消息:5
写入消息:7
读取消息:6
写入消息:8
读取消息:7
写入消息:9
读取消息:8
读取消息:9
读取消息:end
主进程结束
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具