我们需要用到并发编程的地方无非两个原因:1、需要用到大量计算资源,成为计算(/CPU)密集型。2、需要处理一些耗时的操作,比如读写磁盘、接收网络数据,成为吞吐(/IO)密集型。
而实现并发编程的方法总的来说也可以分为两类:通过多线程解决,或者通过多进程来解决。
由于python存在全局解释器锁(GIL)的原因,其实python的多线程更适合于IO密集型的任务,因为一个解释器在任何时候只能有一个线程在占有CPU,就是Python的多线程程序并不能利用多核CPU的优势 (比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。
解决GIL问题有两个途径,一个是使用multiprocessing模块来创建多个进程,另一种是使用C扩展程序来完成独立于python代码的计算工作。
接下来先总结多线程中遇到的一些包、方法。
一、python的多线程
threading包:Thread,但是线程操作比较局限,无法结束一个线程、给它发信号、调整调度等高级操作;
为了解决线程间通信问题:推荐使用队列Queue,它可以保证线程安全的数据信息交换,程序员无需自己现实复杂的锁和同步机制。如果在线程通信中需要使用自定义数据结构,可以使用Event对象(建议只做一次性使用)、Condition对象来给自定义数据结构添加同步机制;使用Lock对象、Semaphore对象给自定义数据结构添加锁机制。
concurrent.futures包:ThreadPoolExecutor创建线程池;
二、python的多进程
multiprocessing包:Process; Pool
concurrent.futures包:ProcessPoolExecutor创建进程池;
三、Actor分布式并行计算框架
Actor模式的分布式并行计算框架:每个actor实例send “消息”,内部线程处理“消息”。
Actor模式允许在任意一个actor实例中运行任意的计算操作(函数).
定义一个简单的Actor模型:
from queue import Queue
from threading import Thread, Event
# Sentinel used for shutdown
class ActorExit(Exception):
pass
class Actor:
def __init__(self):
self._mailbox = Queue()
def send(self, msg):
'''
Send a message to the actor
'''
self._mailbox.put(msg)
def recv(self):
'''
Receive an incoming message
'''
msg = self._mailbox.get()
if msg is ActorExit:
raise ActorExit()
return msg
def close(self):
'''
Close the actor, thus shutting it down
'''
self.send(ActorExit)
def start(self):
'''
Start concurrent execution
'''
self._terminated = Event()
t = Thread(target=self._bootstrap)
t.daemon = True
t.start()
def _bootstrap(self):
try:
self.run()
except ActorExit:
pass
finally:
self._terminated.set()
def join(self):
self._terminated.wait()
def run(self):
'''
Run method to be implemented by the user
'''
while True:
msg = self.recv()
# Sample ActorTask
class PrintActor(Actor):
def run(self):
while True:
msg = self.recv()
print('Got:', msg)
# Sample use
p = PrintActor()
p.start()
p.send('Hello')
p.send('World')
p.close()
p.join()
actor实例就是把“消息”传递给内部线程来处理,消息类型是一个泛型概念:
class TaggedActor(Actor):
def run(self):
while True:
tag, *payload = self.recv()
getattr(self,'do_'+tag)(*payload)
# Methods correponding to different message tags
def do_A(self, x):
print('Running A', x)
def do_B(self, x, y):
print('Running B', x, y)
# Example
a = TaggedActor()
a.start()
a.send(('A', 1)) # Invokes do_A(1)
a.send(('B', 2, 3)) # Invokes do_B(2,3)
每个actor可以执行不同的操作(函数),并且能够将计算结果返回:
from threading import Event
class Result:
def __init__(self):
self._evt = Event()
self._result = None
def set_result(self, value):
self._result = value
self._evt.set()
def result(self):
self._evt.wait()
return self._result
class Worker(Actor):
def submit(self, func, *args, **kwargs):
r = Result()
self.send((func, args, kwargs, r))
return r
def run(self):
while True:
func, args, kwargs, r = self.recv()
r.set_result(func(*args, **kwargs))
# Example use
worker = Worker()
worker.start()
r = worker.submit(pow, 2, 3)
print(r.result())