python 并发编程之进程
一、需要了解的概念
1、进程和程序的区别
程序就是一堆死的东西,没有生命周期
进程是有生命周期的,当一个任务进行完毕之后,进程就不存在了
2、cpu的工作机制
1.当CPU遇到I/O操作的时候,会剥夺CPU的执行权限
I/O密集型:
input output
遇到阻塞,但是不需要占用大量的CPU资源,需要等待,比如:sleep
2.当遇到的任务需要占用大量的计算时间的时候,也会剥夺执行权限
计算密集型
没有遇到阻塞,但是需要占用大量的CPU资源,也不需要等待
3、操作系统的调度算法
1. 先来先服务调度算法
2. 短作业优先调度算法
3. 时间片轮转法
4. 多级反馈队列
二、同步、异步、阻塞(BIO)、非阻塞(NIO)
阻塞(Blocking)和非阻塞(Non-blocking)关注的是任务的执行方式:
- 阻塞:在阻塞模式下,任务会等待某个操作的完成,无法进行其他任务,直到操作完成才能继续执行。任务按照串行方式执行。
- 非阻塞:在非阻塞模式下,任务不会等待操作的完成,而是立即返回并继续执行其他任务。任务可以通过轮询或回调等方式来检查操作是否完成。
同步(Synchronous)和异步(Asynchronous)关注的是任务的通信模式:
- 同步:在同步模式下,任务发出一个请求后,会一直等待结果返回,期间无法执行其他任务。任务的执行顺序是有序的,依赖于前一个任务的结果。
- 异步:在异步模式下,任务发出一个请求后,不需要等待结果返回,可以继续执行其他任务。任务的执行顺序可以是无序的,任务之间相互独立。
可以总结如下:
- 阻塞和非阻塞描述任务的执行方式,阻塞模式下任务会等待操作完成,非阻塞模式下任务会继续执行。
- 同步和异步描述任务的通信模式,同步模式下任务会等待结果返回,异步模式下任务不需要等待结果返回。
- 阻塞和非阻塞主要关注任务自身的执行,同步和异步主要关注任务之间的通信方式。
需要注意的是,阻塞和同步以及非阻塞和异步并不完全等价。一个任务可以是阻塞同步的,即任务在等待操作完成时是阻塞的,而任务的通信方式是同步的。类似地,一个任务可以是非阻塞异步的,即任务在等待操作完成时是非阻塞的,而任务的通信方式是异步的。
同步阻塞式调用 ----> 效率最低
同步非阻塞式调用
异步阻塞式调用
异步非阻塞式调用 ----> 效率最高
进程与线程的一个简单解释 - 阮一峰的网络日志 (ruanyifeng.com)
三、开启进程
1、开启单进程
from multiprocessing import Process def task(): print('this is a task') # 拉起一个进程来完成task函数的操作 if __name__ == '__main__': p = Process(target=task) # 实例化进程对象 p.start() # 开启进程
注意:
- if __name__ == '__main__': 在windos中要上,mac系统可以不加
- multiprocessing 模块导入Process要大写。Process是类,process是方法。
2、Process类的参数
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None): assert group is None, 'group argument must be None for now' count = next(_process_counter) self._identity = _current_process._identity + (count,) self._config = _current_process._config.copy() self._parent_pid = os.getpid() self._popen = None self._target = target self._args = tuple(args) self._kwargs = dict(kwargs) self._name = name or type(self).__name__ + '-' + \ ':'.join(str(i) for i in self._identity) if daemon is not None:
-
group
(可选):分组参数,用于保留未来扩展的目的。通常情况下,该参数可以忽略,传入None
即可。 -
target
(可选):目标函数参数,表示要在新进程中执行的函数。当创建子进程时,该参数指定子进程要执行的代码逻辑。函数必须是可调用的对象。 -
name
(可选):进程名称参数,用于设置进程的名称。可以在创建进程后通过process.name
属性获取或设置进程的名称。 -
* p = multiprocessing.Process(target=worker, name='WorkerProcess')
* print(p.name) # 打印进程名
-
args
(可选):位置参数元组,表示要传递给目标函数的参数。当创建子进程时,该参数传递给目标函数作为位置参数。参数会按照位置顺序传递给目标函数。 -
kwargs
(可选):关键字参数字典,表示要传递给目标函数的关键字参数。当创建子进程时,该参数传递给目标函数作为关键字参数。参数会以关键字参数的方式传递给目标函数。 -
daemon
(可选):守护进程参数,用于设置进程的守护状态。默认值为None
,表示继承父进程的守护状态。如果设置为True
,则子进程将被设置为守护进程,即当父进程结束时,子进程也会随之结束。如果设置为False
,则子进程将是非守护进程,即即使父进程结束,子进程也会继续执行。
3、Process类的方法
from multiprocessing import Process import time def task(): time.sleep(4) print('this is a task') # 拉起一个进程来完成task函数的操作 # if __name__ == '__main__': p = Process(target=task) # 实例化进程对象 p.start() # 开启进程 p.join() # 先执行完子进程中的代码,再执行主进程中的代码 p.terminate() # 杀死进程 print(p.is_alive()) # 查看进程是否存活
4、开启多进程
import time from multiprocessing import Process def task(i): print("task:", i) time.sleep(1) if __name__ == '__main__': start_time = time.time() ll = [] # 定义空列表存多进程 for i in range(5): # 开启多进程之后,有可能是没有顺序的 p = Process(target=task, args=(i,)) # arg是传给目标函数的位置参数,已元组形式传,kwargs以字典形式传 p.start() ll.append(p) for j in ll: j.join() # # 让所有的子进程先执行完,在执行主进程 print(123) print("time:", time.time() - start_time) # 输出结果:5个进程在1秒多执行完后执行主进程的代码 # task: 0 # task: 1 # task: 2 # task: 3 # task: 4 # 123 # time: 1.0228691101074219
四、查看进程的id号
⚠️:1、p.pid 获取的子进程的id号。 2、os.getpid()获取的是当前进程的id号,os.getppid()获取的是当前进程的父进程id号
import os import time from multiprocessing import Process def task(): print('task中的子进程号:', os.getpid()) # 当前进程的id号 print('主进程中的进程号:', os.getppid()) # 当前进程的父进程id号 if __name__ == '__main__': p = Process(target=task) p.start() time.sleep(10) print('子进程的进程号:', p.pid) print('主进程的进程号:', os.getpid())
五、进程间的通信机制(IPC)
在一个进程中不能修改另外一个进程的数据,言外之意是进程之间的数据是隔离的,互不影响
要想在一个进程中使用另外一个进程中的数据,需要让进程之间通信(IPC)
在Python中,有几种方式可以实现进程间通信(IPC,Inter-Process Communication),其中包括使用队列(Queue)、管道(Pipe)、共享内存(Shared Memory)等。
这里以队列为例实现进程间通信:
在主进程中拿到了子进程的a变量,实现进程间的通信
from multiprocessing import Process,Queue def task(q): a = 1 q.put('这是子进程写入的数据!') q.put(a) if __name__ == '__main__': q = Queue(4) p = Process(target=task, args=(q, )) p.start() # 在主进程中取出子进程写的的数据 print(q.get()) print(q.get())