@
目录测试环境:
操作系统: Window 10
工具:Pycharm
Python: 3.7
一、进程池
其实在使用多进程的时候,感觉使用
pool
是最方便的,在多线程中是不存在pool
的。在使用
pool
的时候,可以限制每次的进程数,也就是剩余的进程是在排队,而只有在设定的数量的进程在运行,在默认的情况下,进程是cpu的个数,也就是根据multiprocessing.cpu_count()
得出的结果。
进程池 map() 和 imap() 方法的实现
在
pool
中,有两个方法,一个是map
一个是imap
,其实这两方法超级方便,在执行结束之后,可以得到每个进程的返回结果,但是缺点就是每次的时候,只能有一个参数,也就是在执行的函数中,最多是只有一个参数的,否则,需要使用组合参数的方法。
代码演示:
import multiprocessing
def func(name):
print('start process {}'.format(name))
return name.upper()
if __name__ == '__main__':
p = multiprocessing.Pool(5) # 限制每次执行的进程数为 5 个进程
print(p.map(func,['map','map'])) # 开启一个进程
for i in p.imap(func,['imap','imap']): # 开启了 2 个进程
print(i) # 有两个参数就开启两个进程
运行结果:
在使用
map
的时候,直接返回的一个是一个list
,从而这个list
也就是函数执行的结果,而在imap
中,返回的是一个由结果组成的迭代器,如果需要使用多个参数的话,那么估计需要*args
,从而使用参数args
。
进程池 apply_async() 的实现
在使用
apply_async()
的时候,可以直接使用多个参数
代码演示:
#!/usr/bin/env python
import multiprocessing
import time
def func(name,plan):
print('start process {} 计划'.format(plan))
time.sleep(2)
return name.upper()
if __name__ == '__main__':
results = []
p = multiprocessing.Pool(5) # 限制运行的进程数量为 5 个
for i in range(7): # 开启 7 个进程,而 7 个进程超过了 5 个,所以限制了 2 个进程,不给放行
res = p.apply_async(func, args=('诸葛亮','B'))
results.append(res)
for i in results:
print(i.get(2.1)) # 限制获取进程返回值的超时时间为 2.1 秒
运行结果:
一次只有 5 个进程在运行,另外 2 个进程,不允许运行。
在进行得到各个结果的时候,注意使用了一个
list
来进行append
,要不然在得到结果get的时候会阻塞进程,从而将多进程编程了单进程,从而使用了一个list
来存放相关的结果,在进行得到get
数据的时候,可以设置超时时间,也就是get(timeout=5)
,这种设置。
这里使用列表来存储队列 Queue
对象,应该是提前用了 Queue 模块的 put()
方法,然后返回一个队列对象。
————————————————————————————————————————
二、简单 Process 方法
多进程的类
Process
和多线程的类Thread
差不多的方法,两者的接口基本相同,具体看以下的代码:
代码演示:
#!/usr/bin/env python
from multiprocessing import Process
import os
import time
def func(name):
print('start a process {}'.format(name))
time.sleep(3)
print('the process parent id :',os.getppid())
print('the process id is :',os.getpid())
if __name__ =='__main__':
processes = []
for i in range(2):
p = Process(target=func,args=(i,))
processes.append(p)
for i in processes:
i.start()
print('start all process')
for i in processes:
i.join()
#pass
print('all sub process is done!')
运行结果
在上面例子中可以看到,多进程和多线程的API接口是一样一样的,显示创建进程,然后进行
start
开始运行,然后join
等待进程结束。
在需要执行的函数中,打印出了进程的id和pid,从而可以看到父进程和子进程的
id
号,在linux中,进程主要是使用fork
出来的,在创建进程的时候可以查询到父进程和子进程的id
号,而在多线程中是无法找到线程的id
。
python 多线程不是无法找到 id,而是因为多线程是数据共享的,所以所有的多线程都是共享一个共享 id
参考链接:
获得进程id_浅谈python中的多线程和多进程(二)
Tips:
在Linux操作系统中查询的id的时候,最好用
pstree
,清晰。
三、守护进程堵塞 join 理解
代码演示:
import multiprocessing
import time
def B(name):
for i in range(5): # 执行 10 次 B 计划
print("{}执行B 计划……".format(name))
#time.sleep(0.5)
if __name__ == '__main__':
funB = multiprocessing.Process(target=B, args=("诸葛亮",))
# 设置进程守护
funB.daemon = True
funB.start()
#time.sleep(3)
funB.join() # 选择注释,来测试有 join 和无 join 的情况
print("A 计划完毕……")
运行结果:
无join 的结果
有join 的结果
在进行运行的时候,可以看到,如果没有
join
语句,那么主进程是不会等待守护子进程结束的,而是独自执行,除非为守护进程加join()
,但其实无论是哪种进程加了join()
方法,其实都是可以堵塞主进程的,而它们堵塞的原理如下所示:(特别需要注意的是sleep
线程睡眠的等效于join
堵塞,不过更灵活)
在这里进程堵塞,是由系统将在工作队列内的主进程安排进了等待队列内,导致只执行在工作队列内的子进程,而不执行主进程,从而形成了一个主进程堵塞的现象。
相关引用如下所示:
第一步:当进程A执行到创建
socket
的语句时,操作系统会创建一个由文件系统管理的socket
对象(如下图)。这个socket对象包含了发送缓冲区、接收缓冲区、等待队列等成员。
等待队列是个非常重要的结构,它指向所有需要等待该socket事件的进程。
第二步:当程序执行到recv时,操作系统会将进程A从工作队列移动到该socket的等待队列中(如下图)。
由于工作队列只剩下了进程B和C,依据进程调度,cpu会轮流执行这两个进程的程序,不会执行进程A的程序。
所以进程A被阻塞,不会往下执行代码,也不会占用cpu资源。
参考链接:
终于想明白操作系统的中断和进程阻塞了
四、进程的 sleep() 线程睡眠理解
sleep()
针对的是线程,又因为每一个进程都有一个主线程,所以即使是在主进程创建子进程后,在子进程的执行函数内,sleep()
线程睡眠仍旧是对子进程起作用,因为sleep()
线程睡眠针对的是子进程的主线程。