1, 多进程 vs 多线程
Python中的常见的并发模型分为两种:
(1)IO密集 vs CPU密集
IO密集:
I/O bound 指的是系统的CPU效能相对硬盘/内存的效能要好很多,此时,系统运作,大部分的状况是 CPU 在等 I/O (硬盘/内存) 的读/写,此时 CPU Loading 不高。
IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
CPU密集:
CPU bound 指的是系统的 硬盘/内存 效能 相对 CPU 的效能 要好很多,此时,系统运作,大部分的状况是 CPU Loading 100%,CPU 要读/写 I/O (硬盘/内存),I/O在很短的时间就可以完成,而 CPU 还有许多运算要处理,CPU Loading 很高。
CPU bound密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
在多重程序系统中,大部份时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
(2)多进程 vs 多线程
首先,要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。
如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。
如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。
多进程稳定性好,但是资源代价大
多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。
多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork
调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。
多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程。
在Windows下,多线程的效率比多进程要高,所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题,IIS的稳定性就不如Apache。为了缓解这个问题,IIS和Apache现在又有多进程+多线程的混合模式,真是把问题越搞越复杂。
多线程CPU切换频率高
操作系统在切换进程或者线程时,它需要先保存当前执行的现场环境(CPU寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,但是也需要耗费时间。如果有几千个任务同时进行,操作系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。所以,多任务一旦多到一个限度,就会消耗掉系统所有的资源,结果效率急剧下降,所有任务都做不好。
Python中多线程的伪多线程
由于GIL,是伪多线程。如果使用多线程,所有的计算只会在一个CPU核上,无法真正利用CPU多核。
想要充分利用多核CPU资源,Python中大部分情况下都需要使用多进程,Python中提供了multiprocessing这个包实现多进程。
2,多进程基本用法
Python中提供了multiprocessing这个包实现多进程。multiprocessing支持子进程、进程间的同步与通信,提供了Process、Queue、Pipe、Lock等组件。
Python的multiprocessing库通过以下几步创建进程:
start()
方法,开启进程的活动join()
方法,在进程结束之前一直等待实例方法:
is_alive():返回进程是否在运行。
join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。
start():启动一个子进程。准备就绪,等待CPU调度
run():不启动子进程,直接执行函数。如果实例进程时未制定传入target,这star执行t默认run()方法。
terminate():不管任务是否完成,立即停止工作进程
属性:
authkey
daemon:和线程的setDeamon功能一样
exitcode(进程在运行时为None、如果为–N,表示被信号N结束)
name:进程名字。
pid:进程号。
和多线程一样,多线程multiprocessing模块也有两种基本办法创建子进程:
from multiprocessing import Process
import os
def func(i):
print("pid: {}, execute: {} * {} = {}".format(os.getpid(), i, i, i*i))
if __name__ == "__main__":
data = [1, 2, 3, 4, 5]
process_list = []
for d in data:
p = Process(target=func, args=(d,))
process_list.append(p)
for process in process_list:
process.start()
for process in process_list:
process.join()
实现一个自定义的进程子类,需要以下三步:
1>定义 Process
的子类
2>覆盖 __init__(self [,args])
方法来添加额外的参数
3>覆盖 run(self, [.args])
方法来实现 Process
启动的时候执行的任务
from multiprocessing import Process
import os, time
class MyProcess(Process):
def __init__(self, target=None, args=(), kwargs={}):
super(MyProcess, self).__init__()
self.target = target
self.args = tuple(args)
self.kwargs = dict(kwargs)
def run(self):
if self.target:
print("func {} is running at {}".format(self.target.__name__, time.ctime()))
return self.target(*self.args, **self.kwargs)
def func_test(i):
name = multiprocessing.current_process().name
print("{} pid: {}, execute: {} * {} = {}".format(name, os.getpid(), i, i, i * i))
if __name__ == "__main__":
data = [1, 2, 3, 4, 5]
process_list = []
for d in data:
p = MyProcess(target=func, args=(d,))
process_list.append(p)
for process in process_list:
process.start()
for process in process_list:
process.join()
3,多进程数据同步原语
进程的同步原语和线程的库很类似:
acquire()
和 release()
,来控制共享数据的读写权限。Event
对象有两个方法, set()
和 clear()
,来管理自己内部的变量。wait()
用来等待进程, notify_all()
用来通知所有等待此条件的进程。Threading
模块一样。Lock,Rlock,Event,Condition,Semaphore几个进程同步原语的用法和多线程基本完全一致,只需要将threading.Thread对象换成multiprocessing.Process对象即可。
请参考前一篇博客:https://blog.csdn.net/biheyu828/article/details/83019392
示例代码:
使用Barrier栅栏控制多进程并发执行
import multiprocessing
import time
from multiprocessing import Process
from multiprocessing import Barrier, Lock
def run_with_barrier(barrier):
proc_name = multiprocessing.current_process().name
barrier.wait() ##当两个进程p都调用 wait() 方法的时候,它们会一起继续执行
time.sleep(3)
print("process {} ----> {}".format(proc_name, time.time()))
def run_without_barrier():
proc_name = multiprocessing.current_process().name
time.sleep(3)
print("process {} ----> {}".format(proc_name, time.time()))
if __name__ == "__main__":
barrier = Barrier(2)
lock = Lock()
process_list = []
pro_1 = Process(name="process_1_barrier",target=run_with_barrier, args=(barrier,))
pro_2 = Process(name="process_2_barrier",target=run_with_barrier, args=(barrier,))
pro_3 = Process(name="process_3_no_barrier",target=run_without_barrier)
pro_4 = Process(name="process_4_no_barrier",target=run_without_barrier)
process_list.append(pro_1)
process_list.append(pro_2)
process_list.append(pro_3)
process_list.append(pro_4)
for pro in process_list:
pro.start()
for pro in process_list:
pro.join()
运行结果:
process process_3_no_barrier ----> 1540048781.565112
process process_1_barrier ----> 1540048781.565113
process process_2_barrier ----> 1540048781.565102
process process_4_no_barrier ----> 1540048781.5670989
从运行结果可以看出pro_1和pro_2到达了barrier几乎同时运行,但是pro_3和pro_4进程时间差别较大。
4,多进程交换数据(queue/ pipe)
不同进程之间内存是不共享的。在多进程中直接使用线程类似的方式共享数据,会出现报错,全局变量并不能在不同进程间共享。
import multiprocessing
from multiprocessing import Process
import random
import time
items = []
lock = multiprocessing.Lock()
class Consumer(Process):
def __init__(self):
super(Consumer, self).__init__()
def run(self):
global items
global lock
with lock:
print("items in consumer is: {}".format(items))
data = items.pop()
print("consume data: {} at: {} ".format(data, time.ctime()))
class Producer(Process):
def __init__(self):
super(Producer, self).__init__()
def run(self):
global lock
global items
with lock:
data = random.randrange(1, 1000)
items.append(data)
print("items in producer is: {}".format(items))
print("produce data: {} at: {} ".format(data, time.ctime()))
if __name__ == "__main__":
producer_pro = Producer()
consumer_pro = Consumer()
producer_pro.start()
consumer_pro.start()
producer_pro.join()
consumer_pro.join()
输出如下:
Process Consumer-2:
items in producer is: [214]
produce data: 214 at: Wed Oct 17 22:26:54 2018
items in consumer is: [] ##全局变量items并没有共享给consumer
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/multiprocessing/process.py", line 249, in _bootstrap
data = items.pop()
IndexError: pop from empty list
Multiprocessing库有两个Communication Channel可以交换对象:
Queue
返回一个进程共享的队列,是线程安全的,也是进程安全的。任何可序列化的对象(Python通过 pickable
模块序列化对象)都可以通过它进行交换。Queue和pipe的区别:
Queue:主要用于多对多数据交换,例如多生产者->多消费者模型
pipe:主要用于单对单数据交换,例如单生产者->单消费者模型。在数据交换效率上,pipe比queue要高。同样的数据量使用queue大约是pipe的3倍左右。
(1)多进程Queue用法
内置三种类型的队列:
Queue
:FIFO(先进先出);LifoQueue
:LIFO(后进先出);PriorityQueue
:优先级最小的先出;构造函数一样,都是只有一个maxsize=0,用于设置队列的容量,
如果设置的maxsize小于1,则表示队列的长度无限长。
两个异常:
相关函数
代码示例:
import random, time
import multiprocessing
from multiprocessing import Process
class Consumer(Process):
def __init__(self, queue):
super(Consumer, self).__init__()
self._queue = queue
def run(self):
data = self._queue.get()
print("consume data: {} at: {} ".format(data, time.ctime()))
class Producer(Process):
def __init__(self, queue):
super(Producer, self).__init__()
self._queue = queue
def run(self):
data = random.randrange(11111, 999999)
self._queue.put(data)
print("produce data: {} at: {} ".format(data, time.ctime()))
if __name__ == "__main__":
queue = multiprocessing.Queue()
producer_pro = Producer(queue)
consumer_pro = Consumer(queue)
producer_pro.start()
consumer_pro.start()
producer_pro.join()
consumer_pro.join()
(2)多进程Pipe用法
Pipe对象常用函数
Pipe() | Pipe 方法返回(conn1, conn2)代表一个管道的两端。PIPE方法有个deplex参数,如果deplex参数为True(默认值),那么这个管道是全双工模式,也就是说conn1和conn2均可收发。duplex为False,conn1只负责接收消息,conn2负责发送消息。 |
send() | 向connection发数据 |
recv() | 从connection接收数据 |
close() | 关闭connection |
代码示例:
from multiprocessing import Pipe, Process
def consumer(input_pipe):
while True:
try:
data = input_pipe.recv()
print("consume data: {}".format(data))
except EOFError:
break
def producer(output_pipe, sequence_data):
for data in sequence_data:
output_pipe.send(data)
print("produce data: {}".format(data))
if __name__ == "__main__":
(input_pipe, output_pipe) = Pipe(False)
sequence_data = [1,2,3,4,5]
consumer_pro = Process(target=consumer, args=(input_pipe,))
producer_pro = Process(target=producer, args=(output_pipe,sequence_data))
consumer_pro.start()
producer_pro.start()
consumer_pro.join()
producer_pro.join()
5,进程池
进程池有两种实现方式:
比较:
(1)使用multiprocessing自带的Pool类创建进程池
多进程库提供了 Pool
类来实现简单的多进程任务。 Pool
类有以下方法:
apply() | 该函数用于传递不定参数,主进程会被阻塞直到函数执行结束,实际上这也就说所谓的同步执行。 同步执行,按照加入进程池的顺序执行事件,每次执行完一个再执行另一个,可以获取事件返回值 |
apply_async() | 与apply用法一样,但它是非阻塞且支持结果返回进行回调;实际上也就是异步执行。 异步执行,同时启动进程池中多个进程执行事件,apply_async()可以获取事件返回进度(ApplyResult)对象。任务执行完成以后,使用ApplyResult对象的get()方法获取返回值。 apply_async方式提供了一写获取进程函数状态的函数:ready()、successful()、get() |
map() | 与内置map函数用法基本一致,它融合了map函数和apply_async()函数的功能;它会使进程阻塞直到返回结果。 注意:虽然第二个参数是一个迭代器,但实际应用中,必须在整个队列就绪后,程序才会运行子进程。 |
map_async() | 这是 map_async方式也提供了一写获取进程函数状态的函数:ready()、successful()、get() |
close() | 关闭进程池,阻止更多的任务提交到进程池Pool,待任务完成后,工作进程会退出 |
terminate() | 结束工作进程,不再处理未完成的任务 |
join() | 等待工作线程的退出,必须在close()或terminate()之后使用,因被终止的进程需要被父进程调用wait(join等价于wait),否则进程会成为僵尸进程。 |
注意:
用法一:使用apply()添加进程
示例代码:
import time
import multiprocessing
from multiprocessing import Pool
def data_ready(data):
time.sleep(1)
print("{} execute {} at {}".format(multiprocessing.current_process().name, data, time.ctime()))
return "value_"+str(data)
if __name__ == "__main__":
data_list = [1, 2, 3, 4, 5]
pool = Pool(processes=3)
result_list = []
for data in data_list:
result = pool.apply(data_ready, args=(data,))
print(result)
result_list.append(result)
pool.close() #关闭进程池,禁止添加新任务
pool.join() #等待子进程全部结束之后, 再继续主进程
print("main process finish")
运行结果:
ForkPoolWorker-1 execute 1 at Sun Oct 21 21:04:31 2018
value_1
ForkPoolWorker-2 execute 2 at Sun Oct 21 21:04:32 2018
value_2
ForkPoolWorker-3 execute 3 at Sun Oct 21 21:04:33 2018
value_3
ForkPoolWorker-1 execute 4 at Sun Oct 21 21:04:34 2018
value_4
ForkPoolWorker-2 execute 5 at Sun Oct 21 21:04:35 2018
value_5
main process finish
从执行结果可以看出,进程池中共有3个进程在串行逐个执行。每执行完一个进程,且返回结果后才执行下一个进程,相当于单线程。
用法二:使用apply_sync()添加进程
apply_sync()和 apply()方式方法基本一致,不同的是apply_sync()是异步执行。并且返回值不一样,apply_sync()不是直接返回执行结果,而是一个为进度对象(ApplyResult)对象
示例代码:
import time
import multiprocessing
from multiprocessing import Pool
def data_ready(data):
time.sleep(1)
print("{} execute {} at {}".format(multiprocessing.current_process().name, data, time.ctime()))
return "value_"+str(data)
def callback():
print("this is call back")
if __name__ == "__main__":
data_list = [1, 2, 3, 4, 5]
pool = Pool(processes=3)
result_list = []
for data in data_list:
result = pool.apply_async(data_ready, args=(data,)) ##返回ApplyResult对象
print(result)
result_list.append(result)
for res in result_list:
print(res.get())
pool.close() #关闭进程池,禁止添加新任务
pool.join() #等待子进程全部结束之后, 再继续主进程
print("main process finish")
执行结果:
ForkPoolWorker-2 execute 2 at Mon Oct 22 10:54:35 2018
ForkPoolWorker-1 execute 1 at Mon Oct 22 10:54:35 2018
ForkPoolWorker-3 execute 3 at Mon Oct 22 10:54:35 2018
value_1
value_2
value_3
ForkPoolWorker-2 execute 5 at Mon Oct 22 10:54:36 2018
ForkPoolWorker-1 execute 4 at Mon Oct 22 10:54:36 2018
value_4
value_5
main process finish
用法3: 使用map()启动进程
map(self, func, iterable, chunksize=None)接受一个可迭代对象作为参数,并且返回全部子进程的执行结果列表
示例代码:
import time
import multiprocessing
from multiprocessing import Pool
def data_ready(data):
time.sleep(1)
print("{} execute {} at {}".format(multiprocessing.current_process().name, data, time.ctime()))
return "value_"+str(data)
if __name__ == "__main__":
data_list = [1, 2, 3, 4, 5]
pool = Pool(processes=3)
result = pool.map(data_ready, data_list)
print(result)
运行结果:
ForkPoolWorker-1 execute 1 at Mon Oct 22 11:43:12 2018
ForkPoolWorker-2 execute 2 at Mon Oct 22 11:43:12 2018
ForkPoolWorker-3 execute 3 at Mon Oct 22 11:43:12 2018
ForkPoolWorker-1 execute 4 at Mon Oct 22 11:43:13 2018
ForkPoolWorker-2 execute 5 at Mon Oct 22 11:43:13 2018
['value_1', 'value_2', 'value_3', 'value_4', 'value_5']
用法4:使用map_async()启动进程
与map()不同的是map_async()返回的是MapResult对象,使用该对象的get()方法可以获取执行结果。与apply_async类似,也可以使用callback函数返回子进程执行结果。当子进程执行结束之后自动调用回掉函数。
示例代码:
import time
import multiprocessing
from multiprocessing import Pool
def data_ready(data):
time.sleep(1)
print("{} execute {} at {}".format(multiprocessing.current_process().name, data, time.ctime()))
return "value_"+str(data)
if __name__ == "__main__":
data_list = [1, 2, 3, 4, 5]
pool = Pool(processes=3)
result = pool.map_async(data_ready, data_list)
print(result) ##返回MapResult对象
print(result.get())
运行结果:
ForkPoolWorker-1 execute 1 at Mon Oct 22 11:46:17 2018
ForkPoolWorker-2 execute 2 at Mon Oct 22 11:46:17 2018
ForkPoolWorker-3 execute 3 at Mon Oct 22 11:46:17 2018
ForkPoolWorker-3 execute 5 at Mon Oct 22 11:46:18 2018
ForkPoolWorker-1 execute 4 at Mon Oct 22 11:46:18 2018
['value_1', 'value_2', 'value_3', 'value_4', 'value_5']
callbak返回子进程执行结果示例:
import time
import multiprocessing
from multiprocessing import Pool
def data_ready(data):
time.sleep(1)
print("{} execute {} at {}".format(multiprocessing.current_process().name, data, time.ctime()))
return "value_"+str(data)
def call_back(result):
print("result is: {} finished at {}".format(result, time.ctime()))
return result
if __name__ == "__main__":
data_list = [1, 2, 3, 4, 5]
pool = Pool(processes=3)
pool.map_async(data_ready, data_list, callback=call_back) ##callback函数可以返回子进程的执行结果
pool.close() #关闭进程池,禁止添加新任务
pool.join() #等待子进程全部结束之后, 再继续主进程
print("main process finish")
运行结果:
ForkPoolWorker-1 execute 1 at Mon Oct 22 11:57:57 2018
ForkPoolWorker-2 execute 2 at Mon Oct 22 11:57:57 2018
ForkPoolWorker-3 execute 3 at Mon Oct 22 11:57:57 2018
ForkPoolWorker-1 execute 4 at Mon Oct 22 11:57:58 2018
ForkPoolWorker-2 execute 5 at Mon Oct 22 11:57:58 2018
result is: ['value_1', 'value_2', 'value_3', 'value_4', 'value_5'] finished at Mon Oct 22 11:57:58 2018
main process finish
(2)使用concurrent.futures模块中ProcessPoolExecutor类创建进程池
使用方式和ThreadPoolExecutor一致。
示例代码:
import time
import multiprocessing
from concurrent.futures import ProcessPoolExecutor, wait
def data_ready(data):
time.sleep(1)
print("{} execute {} at {}".format(multiprocessing.current_process().name, data, time.ctime()))
return "value_"+str(data)
if __name__ == "__main__":
data_list = [1, 2, 3, 4, 5]
pool = ProcessPoolExecutor(max_workers=3)
with pool as executor:
task_list = [executor.submit(data_ready, data) for data in data_list]
wait(task_list)
for task in task_list:
print(task.result()) ##获取任务执行结果
print("main process finish")
运行结果:
Process-2 execute 2 at Mon Oct 22 12:09:33 2018
Process-3 execute 3 at Mon Oct 22 12:09:33 2018
Process-1 execute 1 at Mon Oct 22 12:09:33 2018
Process-2 execute 4 at Mon Oct 22 12:09:34 2018
Process-1 execute 5 at Mon Oct 22 12:09:34 2018
value_1
value_2
value_3
value_4
value_5
main process finish
6, multiprocessing块中的多线程dummy
multiprocessing.dummy类实现了多线程功能,用法和multiprocessing多进程类似,api 都是通用的。 可以很方便将代码在多线程和多进程之间切换。
multiprocessing.dummy除了多线程的基本功能外,也提供了线程池Pool功能。
线程池Pool的使用有四种方式:apply_async、apply、map_async、map。其中apply_async和map_async是异步的,也就是启动进程函数之后会继续执行后续的代码不用等待进程函数返回。apply_async和map_async方式提供了一写获取进程函数状态的函数:ready()、successful()、get()。
示例代码:
import time
import multiprocessing.dummy as dum
from multiprocessing.dummy import Pool
def data_ready(data):
time.sleep(1)
print("{} execute {} at {}".format(dum.current_process(), data, time.ctime()))
return "value_"+str(data)
def call_back(result):
print("result is: {} finished at {}".format(result, time.ctime()))
return result
if __name__ == "__main__":
data_list = [1, 2, 3, 4, 5]
pool = Pool(processes=3)
pool.map_async(data_ready, data_list, callback=call_back) ##callback函数可以返回执行结果
pool.close() #关闭线程池,禁止添加新任务
pool.join() #等待线程全部结束之后, 再继续主进程
print("main process finish")
运行结果:
execute 3 at Mon Oct 22 13:23:24 2018
execute 1 at Mon Oct 22 13:23:24 2018
execute 2 at Mon Oct 22 13:23:24 2018
execute 4 at Mon Oct 22 13:23:25 2018
execute 5 at Mon Oct 22 13:23:25 2018
result is: ['value_1', 'value_2', 'value_3', 'value_4', 'value_5'] finished at Mon Oct 22 13:23:25 2018
main process finish
7, 多进程数据共享
Python中多进程数据共享主要有两种方式:
用法一:内存共享
在多进程情况下,由于每个进程有自己独立的内存空间,怎样能实现内存共享呢?multiprocessing模块提供了Value, Array,这两个是函数,详细定义在sharedctypes.py里。ctypes是Python的一个外部函数库,它提供了和C语言兼任的数据类型,可以调用DLLs或者共享库的函数,能被用作在python中。
(1) Value
Value的初始化非常简单,直接类似Value('d', 0.0)即可,具体构造方法如下:
multiprocessing.Value(typecode_or_type, *args[,lock])
返回从共享内存中分配的一个ctypes 对象。其中typecode_or_type定义了返回的类型,它要么是一个ctypes类型,要么是一个代表ctypes类型的code。
*args是传递给ctypes的构造参数
比如整数1,可用Value('h',1)
对于共享整数或者单个字符,初始化比较简单,参照下图映射关系:
Type Code | C Type | Python Type |
'c' | char | character |
'b' | signed char | int |
'B' | unsigned char | int |
'u' | Py_UNICODE | unicode character |
'h' | signed short | int |
'H' | unsigned short | int |
'i' | signed int | int |
'I' | unsigned int | int |
'l' | signed long | int |
'L' | unsigned long | int |
'f' | float | float |
'd' | double | float |
如果共享的是字符串,则在上表是找不到映射关系的,就是没有对应的Type code可用。所以我们需要使用原始的ctype类型。
比如上面的Value('h',1)也可以用Value(c_short,1),字符串的话,可以用Value(c_char_p,"hello"),很好理解的。
它返回的是个对象,所以,它也有一些属性和方法:
value | 获取值 |
get_lock() | 获取锁对象 |
acquire() | 获取锁 |
release() | 释放锁 |
ctype类型对应关系如下:
ctypes type | C type | Python type |
c_bool |
_Bool | bool (1) |
char | char | 1-character string |
c_wchar | wchar_t | 1-character unicode string |
c_byte | char | int/long |
c_ubyte | unsigned char | int/long |
c_short | short | int/long |
c_ushort | unsigned short | int/long |
c_int | int | int/long |
c_uint | unsigned in | int/long |
c_long | long | int/long |
c_ulong | unsigned long | int/long |
c_longlong | __int64 or long long | int/long |
c_ulonglong | unsigned __int64 or unsigned long long | int/long |
c_float | float | float |
c_double | double | float |
c_longdouble | long double | float |
c_char_p | char * (NUL terminated) | string or None |
c_wchar_p |
wchar_t * (NUL terminated) | unicode or None |
c_void_p | void * | int/long or None |
(2)Array
它返回从共享内存分配的ctypes数组, 构造函数:
multiprocessing.Array(typecode_or_type, size_or_initializer, *,lock=True)
typecode_or_type确定返回数组的元素的类型:它是一个ctypes类型或一个字符类型代码类型的数组模块使用的类型。
size_or_initializer:如果它是一个整数,那么它确定数组的长度,并且数组将被初始化为零。否则,size_or_initializer是用于初始化数组的序列,其长度决定数组的长度。
如果关键字参数中有lock的话,lock为True,则会创建一个新的锁对象,以同步对该值的访问。如果lock是Lock或RLock对象,那么它将用于同步对该值的访问。如果lock是False,那么对返回的对象的访问不会被锁自动保护,因此它不一定是“进程安全的”。
示例代码:
import multiprocessing
from multiprocessing import Process, Value, Array, Lock
def worker_value(share_value, lock):
with lock:
share_value.value += 1
print("current process: {} share_value is: {}".format(multiprocessing.current_process().name, share_value.value))
def work_array(share_array, lock):
with lock:
for i in range(len(share_array)):
share_array[i] = share_value.value*i
print("current process: {} share_array is: {}".format(multiprocessing.current_process().name, share_array[i]))
if __name__ == "__main__":
share_value = Value('i', 1) # 整型数字1
share_array = Array('h', 10) # 表示开辟3个空间,且均为整型,其实就是一个列表
lock_1 = Lock() #创建共享锁
lock_2 = Lock()
proc_list = []
for i in range(3):
proc_1 = Process(target=worker_value, args=(share_value, lock_1))
proc_list.append(proc_1)
proc_2 = Process(target=work_array, args=(share_array, lock_2))
proc_list.append(proc_2)
for proc in proc_list:
proc.start()
for proc in proc_list:
proc.join()
print("data in share_value: {}".format(share_value.value)) ##share_value.value返回共享Value的值
print("share_array object: {}".format(share_array)) ##此处share_array是一个Array封装对象
array_list = []
for item in share_array:
array_list.append(item)
print("data in share_array: {}".format(array_list))
运行结果:
current process: Process-1 share_value is: 2
current process: Process-2 share_value is: 3
current process: Process-3 share_value is: 4
current process: Process-4 share_array is: 0
current process: Process-4 share_array is: 4
current process: Process-4 share_array is: 8
current process: Process-4 share_array is: 12
current process: Process-4 share_array is: 16
current process: Process-4 share_array is: 20
current process: Process-4 share_array is: 24
current process: Process-4 share_array is: 28
current process: Process-4 share_array is: 32
current process: Process-4 share_array is: 36
data in share_value: 4
share_array object: >
data in share_array: [0, 4, 8, 12, 16, 20, 24, 28, 32, 36]
从以上输出可以看出:
share_value.value()可以直接获取共享Value的返回值
share_array返回的是一个array对象,不能直接获取返回值。
用法二:进程共享
通过Manager()返回的一个manager对象控制一个服务器进程,它保持住Python对象并允许其它进程使用代理操作它们。同时它用起来很方便,而且支持本地和远程内存共享。
Manager模块管理的共享数据类型有:list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Queue, Value和Array,同时还可以共享类的实例对象。
(1)共享简单的dict类型数据
示例代码:
import multiprocessing
from multiprocessing import Manager, Process
def worker(dic_data, key, value):
dic_data[key] = str(key) + "_" + str(value)
print('data in {} is: {}'.format(multiprocessing.current_process().name, dic_data))
if __name__ == "__main__":
mgr = Manager()
data = {"a": "hello", "b": "welcome", "c": "python"}
dict_data = mgr.dict()
task_list = []
for k, v in data.items():
task = Process(target=worker, args=(dict_data, k, v))
task_list.append(task)
for task in task_list:
task.start()
for task in task_list:
task.join()
print("result is: {}".format(dict_data))
运行结果:
data in Process-2 is: {'a': 'a_hello'}
data in Process-3 is: {'a': 'a_hello', 'b': 'b_welcome'}
data in Process-4 is: {'a': 'a_hello', 'b': 'b_welcome', 'c': 'c_python'}
result is: {'a': 'a_hello', 'b': 'b_welcome', 'c': 'c_python'}
以上输出可以看出字典对象dic_data在子进程中共享了同一份数据。
(2) 共享嵌套dict数据
注意:进程间共享嵌套dict数据时,必须每一层dict都需要实例化为Manager().dict()对象。
如下代码,只实例化外层dict为Manager().dict()对象
import multiprocessing
from multiprocessing import Manager, Process
def worker(dic_data, key, value):
dic_data["china"][key] = str(key) + "_" + str(value)
print('data in {} is: {}'.format(multiprocessing.current_process().name, dic_data))
if __name__ == "__main__":
data = {"a": "hello", "b": "welcome", "c": "python"}
mgr = Manager()
dict_data = mgr.dict() ##仅仅设置外层为Manager().dict()对象
dict_data["china"] = data
task_list = []
for k, v in data.items():
task = Process(target=worker, args=(dict_data, k, v))
task_list.append(task)
for task in task_list:
task.start()
for task in task_list:
task.join()
print("result is: {}".format(dict_data))
运行结果:
data in Process-2 is: {'china': {'a': 'hello', 'b': 'welcome', 'c': 'python'}}
data in Process-3 is: {'china': {'a': 'hello', 'b': 'welcome', 'c': 'python'}}
data in Process-4 is: {'china': {'a': 'hello', 'b': 'welcome', 'c': 'python'}}
result is: {'china': {'a': 'hello', 'b': 'welcome', 'c': 'python'}}
仅仅实例化外层dict为Manager().dict()对象,没有得到我们预期的输出结果。
改进代码:
每层dict都实例化为Manager().dict()对象
import multiprocessing
from multiprocessing import Manager, Process
def worker(dic_data, key, value):
dic_data["china"][key] = str(key) + "_" + str(value)
print('data in {} is: {}'.format(multiprocessing.current_process().name, dic_data))
if __name__ == "__main__":
data = {"a": "hello", "b": "welcome", "c": "python"}
mgr = Manager()
dict_data = mgr.dict()
dict_data_inner = mgr.dict()
dict_data["china"] = dict_data_inner
task_list = []
for k, v in data.items():
task = Process(target=worker, args=(dict_data, k, v))
task_list.append(task)
for task in task_list:
task.start()
for task in task_list:
task.join()
print(dict_data)
print("result is: {}".format(dict_data["china"]))
运行结果:
data in Process-2 is: {'china': }
data in Process-3 is: {'china': }
data in Process-4 is: {'china': }
{'china': }
result is: {'a': 'a_hello', 'b': 'b_welcome', 'c': 'c_python'}
(3)共享Value、Array、list、Lock等数据
示例代码:
from multiprocessing import Manager, Process
def worker(share_value, share_list, share_dict, lock):
with lock:
share_value.value += 1
share_dict["a"] = "hello"
for i in range(len(share_list)):
share_list[i] *= 2
if __name__ == "__main__":
mgr = Manager()
share_value = mgr.Value('i', 1) #i为typecode
share_list = mgr.list([1, 2, 3, 4])
share_dict = mgr.dict()
share_array = mgr.Array('i', range(10))
lock=mgr.Lock()
proc_list = []
for i in range(3):
proc = Process(target=worker, args=(share_value, share_list, share_dict, lock))
proc_list.append(proc)
for proc in proc_list:
proc.start()
for proc in proc_list:
proc.join()
print(share_value)
print(share_list)
print(share_dict)
print(share_array)
运行结果:
Value('i', 4)
[8, 16, 24, 32]
{'a': 'hello'}
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
注意:
typecode必须为指定的字符,否则会报错ValueError: bad typecode (must be b, B, u, h, H, i, I, l, L, q, Q, f or d)
参考文献:
https://www.cnblogs.com/gengyi/p/8620853.html
http://blog.51cto.com/11026142/1874807