Python多进程
复习一下操作系统的基本知识,详细的介绍可以看《Operating System Concepts》
- 进程与线程的概念
- 进程与线程的区别,线程上下文切换代价小,一个进程可以有多个线程,同一个进程中的data和code部分是该进程中的线程所共享的,每个线程有自己独立stack和program counter.
- 用户线程与核心线程,一对一,一对多,多对多的关系
- Concurrency和Parallel的区别
fork()
Python中fork()
的用法和UNIX/Linux/Mac
系统中的系统调用fork()
一样。因为os
模块封装了常见的系统调用。
import os
print('Process (%s) start...' % os.getpid())
a = 1
Process (22333) start...
pid = os.fork()
if pid == 0:
print("I am child process (%s) and my parent is %s." % (os.getpid(), os.getppid()))
else:
print("I (%s) just created a child process (%s)." % (os.getpid(), pid))
I (22333) just created a child process (23090).
I am child process (23090) and my parent is 22333.
调用fork()
后,两个进程同时开始运行,两个进程中都有pid
变量,在父进程中,pid
等于子进程的process id,在子进程中,pid
等于0
。这种方式生成的进程继承了父进程的数据,所以数据可以方便的从父进程流动到子进程。
这个两个进程的pid
也可以通过查看Mac系统的Activity Monitor
找到。
有了fork()
调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的APache服务器就是有父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的HTTP请求。
Multiprocessing
from multiprocessing import Process
import os
# 子进程要执行的代码
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')
Parent process 22333.
Child process will start.
Run child process test (23091)...
Child process end
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process
实例,用start()
方法启动。
join()
方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步
Pool
用进程池的方式批量创建子进程
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) # delay the execution for seconds
end = time.time()
print('Task %s runs %0.2f seconds.' (name, (end - start)))
if __name__ == '__main__':
print('Parent process %s.' % os.getpid())
p = Pool()
for i in range(10):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
Parent process 22333.
Run task 0 (23092)
Run task 1 (23093)
Run task 2 (23094)
Run task 4 (23096)
Run task 3 (23095)
Run task 5 (23097)
Run task 6 (23098)
Run task 7 (23099)
Waiting for all subprocesses done...
Run task 8 (23094)
Run task 9 (23098)
All subprocesses done.
p = Pool()
表示允许同时执行的进程个数是默认大小,而Pool
的默认大小就是CPU的核数,这台电脑的CPU是8核的,所以task0-7能同时执行,后面的task需要等到他们结束后才能执行。
如果指定Pool
的大小,p = Pool(10)
,那么这些进程都能同时执行。
子进程
如果子进程不是本身,而是一个外部进程。创建子进程后,还需要控制子进程的输入和输出。需要用到subprocess
模块。
>>> import subprocess
>>> subprocess.call(['nslookup', 'www.python.org'])
Server: 192.168.2.1
Address: 192.168.2.1#53
Non-authoritative answer:
www.python.org canonical name = dualstack.python.map.fastly.net.
Name: dualstack.python.map.fastly.net
Address: 151.101.184.223
0
>>>
Description of subprocess.call() in official doc:
Run the command described by args. Wait for command to complete, then return the returncode
attribute.
Code needing to capture stdout or stderr should use run()
instead:
上面的代码相当于在命令行输入了`nslookup www.python.org’
如果子进程还需要输入,可以通过communicate()
方法输入
import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)
$ nslookup
Server: 192.168.2.1
Address: 192.168.2.1#53
Non-authoritative answer:
python.org mail exchanger = 50 mail.python.org.
Authoritative answers can be found from:
python.org nameserver = ns-1134.awsdns-13.org.
python.org nameserver = ns-2046.awsdns-63.co.uk.
python.org nameserver = ns-484.awsdns-60.com.
python.org nameserver = ns-981.awsdns-58.net.
mail.python.org internet address = 188.166.95.178
mail.python.org has AAAA address 2a03:b0c0:2:d0::71:1
Exit code: 0
相当于在命令行执行命令nslookup
,然后手动输入:
set q=mx
python.org
exit
进程间通信
在上操作系统课程时,了解到进程间通信的有多种底层实现方式,例如消息队列,共享内存等等。
Python的multiprocessing
模块包装了底层的机制,提供了Queue
、Pipes
等多中方式来交换数据。
以Queue
为例,在父进程中创建两个子进程,一个往Queue
里写数据,一个从Queue
里读数据:
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get()
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.start()
pr.start()
# 等待pw结束
pw.join()
# pr进程里是死循环,只能强行终止:
pr.terminate()
print('END')
Process to write: 23117
Process to read: 23118
Put A to queue...
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.
END