文章目录
1. 简介
多进程(multiprocessing)模块是在 Python 2.6 版本中加入的,和多线程(threading)模块类似,都是用来做并行运算的。不过Python既然有了threading,为什么还要搞一个multiprocessing呢?这是因为Python内部有一个全局解释锁(GIL),任何一个进程任何时候只允许一个线程进行CPU运算,如果一个进程中的某个线程在进行CPU运算时获得GIL,其他线程将无法进行CPU运算只能等待,使得多线程无法利用CPU多核的特性。多进程处理实际上对每个任务都会生成一个操作系统的进程,并且每一个进程都被单独赋予了Python的解释器和GIL,所以程序在运行中有多个GIL存在,每个运行者的线程都会拿到一个GIL,在不同的环境下运行,自然也可以被分配到不同的处理器上。
python的多线程适用于IO密集型任务,IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成
多进程适用于CPU密集型任务,也称为计算密集型任务,计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数
2. 多进程编程
2.1 Process类
Process(group, target, name, args, kwargs)
target: 表示调用对象(一个函数,用于处理任务)
args:表示调用对象的位置参数元组
kwargs:表示调用对象的字典
name:为别名
group:实质上不使用
方法
start(): Process以start()启动某个进程
join() :主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用
close() : 关闭pool,使其不在接受新的任务
terminate() : 结束工作进程,不在处理未完成的任务
属性
name:进程名
pid:进程号
daemon:当设置p.daemon = True时,不管子进程有没有运行结束,当主进程结束后子进程都结束;具体看例4
例1. 入门: 创建函数并将其作为单个进程
import multiprocessing
import time
def worker(interval):
n = 2
while n > 0:
print("The time is {0}".format(time.ctime()))
time.sleep(interval)
n -= 1
if __name__ == "__main__":
print("current time:", time.ctime())
# 创建一个进程, 一般设置参数target和args即可
p = multiprocessing.Process(target = worker, args = (3,), name = "worker")
# 启动进程
p.start()
# 打印进程号
print("p.pid:", p.pid)
# 打印进程名
print("p.name:", p.name)
# 进程是否正在运行
print("p.is_alive:", p.is_alive())
time.sleep(10)
# 10秒后进程已经结束
print("p.is_alive:", p.is_alive())
输出
current time: Sat May 30 15:16:04 2020
p.pid: 10692
p.name: worker
p.is_alive: True
The time is Sat May 30 15:16:05 2020
The time is Sat May 30 15:16:08 2020
p.is_alive: False
例2:创建函数并将其作为多个进程
import multiprocessing
import time
def worker_1(interval):
print("worker_1, start time: ", time.ctime())
time.sleep(interval)
print("end worker_1, end time: ", time.ctime())
def worker_2(interval):
print("worker_2, start time: ", time.ctime())
time.sleep(interval)
print("end worker_2, end time: ", time.ctime())
if __name__ == "__main__":
p1 = multiprocessing.Process(target = worker_1, args = (2,))
p2 = multiprocessing.Process(target = worker_2, args = (3,))
p1.start()
p2.start()
# 打印cpu核心数
print("The number of CPU is:" + str(multiprocessing.cpu_count()))
# 遍历每一个运行中的子进程
for p in multiprocessing.active_children():
print("child p.name:" + p.name + "\tp.id" + str(p.pid))
print("END!!!!!!!!!!!!!!!!!")
输出
The number of CPU is:8
child p.name:Process-2 p.id3568
child p.name:Process-1 p.id7968
END!!!!!!!!!!!!!!!!!
worker_1, start time: Sat May 30 15:24:44 2020
end worker_1, end time: Sat May 30 15:24:46 2020
worker_2, start time: Sat May 30 15:24:44 2020
end worker_2, end time: Sat May 30 15:24:47 2020
[Finished in 3.3s]
例3:将进程定义成类
import multiprocessing
import time
def worker_1(interval):
print("worker_1, start time: ", time.ctime())
time.sleep(interval)
print("end worker_1, end time: ", time.ctime())
def worker_2(interval):
print("worker_2, start time: ", time.ctime())
time.sleep(interval)
print("end worker_2, end time: ", time.ctime())
# 定义一个进程
class ClockProcess1(multiprocessing.Process):
def __init__(self, interval):
super(ClockProcess1,self).__init__()
self.interval = interval
def run(self):
#可以直接调用函数,也可以将函数体直接放在run方法里
worker_1(self.interval)
#定义第二个进程
class ClockProcess2(multiprocessing.Process):
def __init__(self, interval):
super(ClockProcess2,self).__init__()
self.interval = interval
def run(self):
worker_2(self.interval)
# 主进程
if __name__ == '__main__':
p1 = ClockProcess1(3)
p2 = ClockProcess2(3)
p1.start()
p2.start()
输出
worker_1, start time: Sat May 30 15:44:33 2020
end worker_1, end time: Sat May 30 15:44:36 2020
worker_2, start time: Sat May 30 15:44:33 2020
end worker_2, end time: Sat May 30 15:44:36 2020
注意:进程p调用start()时,自动调用run()
例4:daemon属性设置后的区别
(1)不设置daemon属性
import multiprocessing
import time
def worker(interval):
print("work start:{0}".format(time.ctime()));
time.sleep(interval)
print("work end:{0}".format(time.ctime()));
# 主进程
if __name__ == "__main__":
p = multiprocessing.Process(target = worker, args = (3,))
p.start()
print("end!", time.ctime())
输出
end! Sat May 30 16:06:48 2020
work start:Sat May 30 16:06:48 2020
work end:Sat May 30 16:06:51 2020
可以发现不设置daemon属性,主进程结束后子进程还在继续执行
(2)设置daemon属性
import multiprocessing
import time
def worker(interval):
print("work start:{0}".format(time.ctime()));
time.sleep(interval)
print("work end:{0}".format(time.ctime()));
# 主进程
if __name__ == "__main__":
p = multiprocessing.Process(target = worker, args = (3,))
p.daemon = True
p.start()
time.sleep(2)
print("end!", time.ctime())
输出
end! Sat May 30 16:10:47 2020
可以看到,主进程结束后,尽管子进程还没结束,整个程序也就结束
注意:子进程程序中会等待子进程所有程序都执行完毕再一起输出,因此上面例子中子进程不会先打印一个时间。
可以使用join()方法等待子进程结束,如下
import multiprocessing
import time
def worker(interval):
print("work start:{0}".format(time.ctime()));
time.sleep(interval)
print("work end:{0}".format(time.ctime()));
# 主进程
if __name__ == "__main__":
p = multiprocessing.Process(target = worker, args = (3,))
p.daemon = True
p.start()
# 等待子进程结束
p.join()
print("end!", time.ctime())
输出
ork start:Sat May 30 16:14:24 2020
work end:Sat May 30 16:14:27 2020
end! Sat May 30 16:14:27 2020
2.2 进程池Pool
在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
- 使用
multiprocessing.Pool()
来创建进程池,可以设置进程池的大小,默认为CPU的核心个数,一般进程也不会超过CPU的核心个数,超过了效率也不会高 - 进程池可以通过
map
和apply_async
方法来调用执行代码,传入之后,就进入了子进程等待队列中,等待执行。返回值是ApplyResult
对象,存储子进程的结果 - ApplyResult对象的
get()
方法,获取子进程的返回值 join()
方法等待所有子进程的结束,调用前必须使用close()
方法
例5. 使用进程池(apply_async&apply)
(1)apply_async(非阻塞)
import multiprocessing
import time
def func(msg, i):
print("msg:", msg, time.ctime())
time.sleep(3)
print("%d end"%i, time.ctime())
if __name__ == "__main__":
#维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
pool = multiprocessing.Pool(processes = 3)
for i in range(2):
msg = "hello %d" %(i)
pool.apply_async(func, (msg, i))
print("Mark~ Mark~ Mark", time.ctime())
pool.close()
#调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
pool.join()
print("Sub-process(es) done.", time.ctime())
输出
Mark~ Mark~ Mark Sat May 30 17:16:34 2020
msg: hello 0 Sat May 30 17:16:34 2020
0 end Sat May 30 17:16:37 2020
msg: hello 1 Sat May 30 17:16:34 2020
1 end Sat May 30 17:16:37 2020
Sub-process(es) done. Sat May 30 17:16:37 2020
(2) apply(阻塞)
import multiprocessing
import time
def func(msg, i):
print("msg:", msg, time.ctime())
time.sleep(3)
print("%d end"%i, time.ctime())
if __name__ == "__main__":
pool = multiprocessing.Pool(processes = 3)
for i in range(2):
msg = "hello %d" %(i)
pool.apply(func, (msg, i))
print("Mark~ Mark~ Mark", time.ctime())
pool.close()
pool.join()
print("Sub-process(es) done.", time.ctime())
输出
Mark~ Mark~ Mark Sat May 30 17:20:49 2020
msg: hello 0 Sat May 30 17:20:43 2020
0 end Sat May 30 17:20:46 2020
msg: hello 1 Sat May 30 17:20:46 2020
1 end Sat May 30 17:20:49 2020
Sub-process(es) done. Sat May 30 17:20:49 2020
从上面两个例子可以看出async_apply,为非阻塞形式,子进程同时执行,而apply为阻塞形式,子进程是串联执行,即一个执行完再执行另一个子程序。apply_async_apply和apply传入的参数第一个为fun, 第二个是fun的参数,以元组形式传入。
例6. 使用进程池并关注结果
import multiprocessing
import time
def func(msg):
print("msg:", msg, "\t", time.ctime())
time.sleep(3)
print("end\t", time.ctime())
return "done" + msg+"\t"+str(time.ctime())
if __name__ == "__main__":
pool = multiprocessing.Pool(processes=4)
result = []
for i in range(3):
msg = "hello %d" %(i)
result.append(pool.apply_async(func, (msg, )))
pool.close()
pool.join()
for res in result:
print(":::", res.get())
print("Sub-process(es) done.")
输出
msg: hello 0 Sat May 30 17:41:24 2020
end Sat May 30 17:41:27 2020
msg: hello 1 Sat May 30 17:41:24 2020
end Sat May 30 17:41:27 2020
msg: hello 2 Sat May 30 17:41:24 2020
end Sat May 30 17:41:27 2020
::: donehello 0 Sat May 30 17:41:27 2020
::: donehello 1 Sat May 30 17:41:27 2020
::: donehello 2 Sat May 30 17:41:27 2020
Sub-process(es) done.
例7.多函数进程
import multiprocessing
import os, time, random
def Lee():
print ("\nRun task Lee-%s" %(os.getpid())+"\t", time.ctime()) #os.getpid()获取当前的进程的ID
start = time.time()
time.sleep(random.random() * 10) #random.random()随机生成0-1之间的小数
end = time.time()
print ('Task Lee, runs %0.2f seconds.' %(end - start))
def Marlon():
print ("\nRun task Marlon-%s" %(os.getpid()),"\t", time.ctime())
start = time.time()
time.sleep(random.random() * 40)
end=time.time()
print ('Task Marlon runs %0.2f seconds.' %(end - start))
def Allen():
print ("\nRun task Allen-%s" %(os.getpid()),"\t", time.ctime())
start = time.time()
time.sleep(random.random() * 30)
end = time.time()
print ('Task Allen runs %0.2f seconds.' %(end - start))
def Frank():
print ("\nRun task Frank-%s" %(os.getpid()),"\t",time.ctime())
start = time.time()
time.sleep(random.random() * 20)
end = time.time()
print ('Task Frank runs %0.2f seconds.' %(end - start))
if __name__=='__main__':
function_list= [Lee, Marlon, Allen, Frank]
# 获取主进程号
print ("parent process %s" %(os.getpid()))
pool=multiprocessing.Pool(4)
for func in function_list:
pool.apply_async(func) #Pool执行函数,apply执行函数,当有一个进程执行完毕后,会添加一个新的进程到pool中
print ('Waiting for all subprocesses done...')
pool.close()
#调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束
pool.join()
print ('All subprocesses done.')
Run task Lee-19936 Sat May 30 17:51:24 2020
Task Lee, runs 8.44 seconds.
Run task Frank-6316 Sat May 30 17:51:24 2020
Task Frank runs 15.17 seconds.
Run task Allen-1996 Sat May 30 17:51:24 2020
Task Allen runs 21.64 seconds.
Run task Marlon-8648 Sat May 30 17:51:24 2020
Task Marlon runs 31.43 seconds.
All subprocesses done.
例8. 使用pool.map()
import multiprocessing
import os, time, random
def compute(num):
return str(num**2)+" "+str(time.ctime())
if __name__=='__main__':
num = [1,2,3,4]
pool=multiprocessing.Pool(3)
result = pool.map(compute,num)
print ('Waiting for all subprocesses done...')
pool.close()
#调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束
pool.join()
print(type(result), "\t", result)
print ('All subprocesses done.'+"\t"+time.ctime())
Waiting for all subprocesses done...
<class 'list'> ['1 Sat May 30 18:03:58 2020', '4 Sat May 30 18:03:58 2020', '9 Sat May 30 18:03:58 2020', '16 Sat May 30 18:03:58 2020']
All subprocesses done. Sat May 30 18:03:58 2020
2.3 Todo:Pipe&Queue&Event&Semaphore&Lock
2.4 注意事项
(1) 在windows下进行多进程编程的时候,主程序一定要加 if name == “main”
(2) 获取cpu核心数,即进程池中可以设置的最大进程数,若设置超过该数并不会加快速度
from multiprocessing import cpu_count, Pool
cores = cpu_count()
2.5 cpu 数量
cores = cpu_count()
3. 多线程编程
使用map方法可以获取多线程的返回值
from concurrent.futures import ThreadPoolExecutor
import time
# 参数times用来模拟网络请求的时间
def get_html(times):
time.sleep(times)
print("get page {}s finished".format(times))
return times
executor = ThreadPoolExecutor(max_workers=2)
urls = [3, 2, 4] # 并不是真的url
for data in executor.map(get_html, urls):
print("in main: get page {}s success".format(data))
输出:
# get page 2s finished
# get page 3s finished
# in main: get page 3s success
# in main: get page 2s success
# get page 4s finished
# in main: get page 4s success
从输出可以看出输出顺序和urls
列表的顺序相同,就算2s的任务先执行完成,也会先打印出3s的任务先完成,再打印2s的任务完成
参考ThreadPoolExecutor线程池
4. 总结
在编程中多进程的实现主要有下面两种方法
(1) multiprocessing.Process
方法,使用这种方法的好处在于可以使用start()
和join()
方法来控制各个子进程的执行顺序,如现在有三任务,执行顺序如下图所示
即任务2只能在任务1之后执行,任务3可以和其他两个任务同时执行,则可以同过如下方式实现。
import multiprocessing
import time
def worker_1(n):
time.sleep(n)
print("task1 finished")
def worker_2(n):
time.sleep(n)
print("task2 finished")
def worker_3(n)
time.sleep(n)
print("task3 finished")
if __name__ == "__main__":
p1 = multiprocessing.Process(target = worker_1, args = (1,))
p2 = multiprocessing.Process(target = worker_2, args = (2,))
p3 = multiprocessing.Process(target = worker_3, args = (3,))
p1.start() # 任务1启动
p3.start() # 任务3启动
p1.join() # 等待1结束
p2.start() # 任务2启动
# 等待剩下所有任务结束
p3.join()
p2.join()
# 所有子任务结束,进入主进程
print("END!!!!!!!!!!!!!!!!!")
(2) 另外一种方法是使用imap
方法,该方法最大好处可以顺序返回结果
import multiprocessing
import time
def get_html(n):
time.sleep(n)
print("{}s sub_progress success".format(n))
return n
if __name__ == "__main__": #注意,在windows下这句话必须加!!!
pool = multiprocessing.Pool(multiprocessing.cpu_count())
# for result in pool.imap_unordered(get_html, [1, 3, 2]):
for result in pool.imap(get_html, [1, 3, 2]):
print("{}s sleep success".format(result))
输出
# 1s sub_progress success
# 1s sleep success
# 2s sub_progress success
# 3s sub_progress success
# 3s sleep success
# 2s sleep success
可以看到,结果是按输入列表的顺序输出。imap
方法,结果输出顺序与传入的可迭代对象的顺序相同。imap_unordered
方法,结果输出顺序与子进程结束的时间顺序相同。
参考多进程编程