我们需要用到并发编程的地方无非两个原因: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())