Python多进程(multiprocessing)

由于Python多线程的弊端和GIL,适合IO密集型,那么对于计算密集型的应用应该怎么办呢?那就是多进程。(每个进程的GIL互不影响,多进程来并行编程。)

1. multiprocessing模块

multiprocessing模块提供了一个Process类来代表一个进程对象。Processv类描述如下: multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)`

函数 用途
group 参数未使用,预留参数,值始终为None
target 表示调用对象,即子进程要执行的任务
name 为子进程的名称
args 表示调用对象的位置参数元组,args=(1,2,'egon',)
kwargs 表示调用对象的字典,kwargs={'name':'egon','age':18}
deamon bool值,表示是否为守护进程
  • 一些常用的函数
函数 用途
p.start() 启动进程,并调用该子进程中的p.run()
p.run() 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate() 强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive() 如果p仍然运行,返回True
p.join([timeout]) 主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能joinstart开启的进程,而不能joinrun开启的进程
  • 一些属性
属性 用途
p.daemon 默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name 进程的名称
p.pid 进程的pid
p.exitcode 进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey 进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功)
# 启动子进程
from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
    print('Run Subprocess %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',))
    print('Subprocess will start.')
    p.start()
    p.join()
    print('Subprocess end.')

执行结果如下:

Parent process 4424.
Subprocess will start.
Run Subprocess test (14896)...
Subprocess end.

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动。join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

2. 进程池

如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool(4)
    for i in range(4):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')

执行结果如下:

Parent process 15148.
Waiting for all subprocesses done...
Run task 0 (2400)...
Run task 1 (13564)...
Run task 2 (6820)...
Run task 3 (15068)...
Task 1 runs 0.40 seconds.
Task 3 runs 0.78 seconds.
Task 2 runs 1.72 seconds.
Task 0 runs 1.84 seconds.
All subprocesses done.

对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
当某个进程fork一个子进程后,该进程必须要调用wait等待子进程结束发送的sigchld信号,对子进程进行资源回收等相关工作,否则,子进程会成为僵死进程,被init收养。所以,在multiprocessing.Process实例化一个对象之后,该对象有必要调用join方法,因为在join方法中完成了对底层wait的处理。

3. 进程间通信

Process需要通信,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

from multiprocessing import Process, Queue

def producer(queue,name,food):
    for i in range(2):
        print(f'{name}生产了{food}{i}')
        res = f'{food}{i}'
        queue.put(res)    # 通过q.put()来入列,该方法支持存入单个变量,也支持通过列表一次入列多个不同类型的元素
    # queue.put(None)     # 当生产者结束生产,在队列的最后做一个标志,告诉消费者停止去队列里取数据

def consumer(queue,name):
    while True:
        res = queue.get() # 通过queue.get()方法来出队列
        # if res == None:
        #     break       # 判断队列拿出的是不是生产者放的结束生产的标识,如果是则不取,直接退出,结束程序
        print(f'{name}吃了{res}')

if __name__ == '__main__':
    queue = Queue()       # 父进程创建Queue,并传给各个子进程
    producer1 = Process(target=producer,args=(queue,'小明','巧克力'))
    consumer1 = Process(target=consumer,args=(queue,'小婷'))
    producer1.start()     # 启动'写入'子进程producer1
    consumer1.start()     # 启动'读取'子进程consumer1
    producer1.join()      # 等待producer1结束
    consumer1.terminate() # consumer1进程里是死循环,无法等待其结束,只能强行终止

执行结果如下:

小明生产了巧克力0
小明生产了巧克力1
小婷吃了巧克力0
小婷吃了巧克力1

posted @ 2023-05-09 20:19  qiuhlee  阅读(71)  评论(0编辑  收藏  举报