要让python实现多进程「multiprocessing」。我们先来了解操作系统相关知识。
Unix 和 Linux 操作系统提供了一个 fork() 函数系统调用,它非常特殊。普通的函数,调用一它次,执行一次,但是 fork() 函数调用一次执行两次,因为操作系统自动把当前进程「称为父进程」复制了一份「称为子进程」,然后,分别在子进程和父进程中执行。
子进程永远返回0,而父进程返回子进程的 ID。这样做的理由是,一个父进程可以 fork() 多个子进程,所以父进程要记下所有子进程的 ID,而子进程只要调用 getppid() 就可以拿到父进程的 ID。
python中 os 模块封装了常见的系统调用,其中就包括 fork(),可以在python程序中轻松创建子程序:
import os
print('Process (%s) start ...' % os.getpid())
#Only work on Unix/linux/Mac
#不能在Windows平台上运行
pid = os.fork()
if pid == 0:
print('I am child process (%) and my parent is %s.' % (os.getpid(),os.getppid()))
else:
print('I (%) just created a child process (%).' % (os.getpid(),pid))
运行结果:
Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.
由于 Windows 平台下没有 fork() 函数调用,所以代码没有办法在 Windows平台下运行。
有了 fork 调用,一个进程在接到任务的时候就可以复杂出来一个子进程来处理新任务,常见的 Apache 服务器就是由父进程监听端口,每当有新的 http 请求时,就 fork 出新的子进程来处理新的 http 请求。
multiprocessing「多进程」
如果你想写多进程的服务程序,Unix/Linux 平台最好了,当然也可以在 Windows 平台下来编写,因为 python 跨平台。multiprocessing 模块就是跨平台版本的多进程模块。
multiprocessing 模块提供了一个 Process 类来代表一个进程对象,下面一个例子用来演示启动一个子进程并等待结束的例子:
import os
from multiprocessing import Process
#子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)' % (name,os.getpid()))
if __name__ == '__main__':
print('parent process %s' % os.getpid())
p = Process(target=run_proc,args=('test',))#创建子程序
print('Child process will start')
p.start()#子程序开始执行
p.join()
print('Child process end.')
- 创建子程序时,只需要传入一个执行的函数和函数的参数。
- 创建一个 Procsess 实例,用 start() 方式开启,这样创建的进程比 fork 还简单。
- join() 方法可以等jinc子进程执行完后再继续往下运行,通常用于进程之间的同步。
Pool
如果想要启动大量的子进程,可以用进程池的方式批量创建子进程。
import os,time,random
from multiprocessing import Pool
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 run %0.2f seconds.' % (name,(end-start)))
if __name__ == '__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task,args=(i,))
print('Waiting for all subprocess done...')
p.close()
p.join()
print('All subprocess done')
执行结果:
Parent process 7600.
Waiting for all subprocess done...
Run task 0 (11392)...
Run task 1 (6432)...
Run task 2 (10768)...
Run task 3 (5116)...
Task 0 run 0.03 seconds.
Run task 4 (11392)...
Task 3 run 1.42 seconds.
Task 1 run 1.77 seconds.
Task 4 run 2.59 seconds.
Task 2 run 2.93 seconds.
All subprocess done
Process finished with exit code 0
- 对Pool调用 join() 方法会等所用子进程执行完毕,调用 join() 之前一定要调用 close() 调用 close() 之后不能在有新的process
- 程序的输出结果显示 task 0、1、2、3 是同时执行的,而 task 4 是等前四个执行完毕才执行,这是因为,进程池在我的电脑上是4。,因此最多执行四个进程,这是 Pool 有意设计的限制,并不是操作系统的限制,如果你改成
p = Pool(5)
就可以同时跑 5 个进程。
子进程
很多时候,子进程并不是本身,热是一个外部的进程。我们创建了子进程之后,还要控制进程的输入和输出。
subprocess 模块可以让我们非常方便的启动一个子进程,然后控制输入和输出。
这一部分未完待续
进程间的通信
Process 间肯定是要通信的,操作系统提供了很多机制来实现进程间的通信,python 中的 multiprocessing 模块包装了底层的机制,提供了 Queue、Pipes 等多种方法来交换数据。
我们以 Queue 为例,在父进程中创建两个子进程,一个往 Queue 里写数据,一个从 Queue 中读数据。
from multiprocessing import Queue,Process
import os,time,random
#写数据进程执行的代码
def write(q):
print('Process to write : %s' % os.getpid())
for value in ['A','B','C']:
print('Put %s queue...' % value)
q.put(value)
time.sleep(random.random())
#读数据执行的代码
def read(q):
print('Process to read : %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__ == '__main__':
#父进程创建Queue,并传给各个子进程
q = Queue()
pw = Process(target=write,args=(q,))
pr = Process(target=read,args=(q,))
#启动子程序pw,写入:
pw.start()
#启动子程序pr,读取:
pr.start()
#等待pw结束:
pw.join()
#pr进程是死循环,无法等待它结束,只能强行终止。
pr.terminate()
运行结果
Process to read : 8416
Process to write : 12840
Put A queue...
Get A from queue.
Put B queue...
Get B from queue.
Put C queue...
Get C from queue.
Process finished with exit code 0
- 在Windows 平台下实现多进程,用multiprocessing 模块
- 进程间的通信是用 Queue 、Pipes 来进行的。