“并發(fā)是一種解耦策略” ???? —— Robert C.Martin
何為Actor模式呢?
引用維基百科的解釋 Actor Model
在計算機科學中,參與者模式(Actor model)是一種并發(fā)運算上的模型。“參與者”是一種程序上的抽象概念,被視為并發(fā)運算的基本單元:當一個參與者接收到一則消息,它可以做出一些決策、創(chuàng)建更多的參與者、發(fā)送更多的消息、決定要如何回答接下來的消息。
這段話很全面,但也需要我們花時間來消化與理解。不妨先試著閱讀以下這段Python代碼實現(xiàn),再分析理解。
from queue import Queue
from threading import Thread
class ActorExit(Exception):
pass
class Actor(object):
def __init__(self):
self._thread = Thread()
self._queue = Queue()
def send(self, msg):
self._queue.put(msg)
def receive(self):
msg = self._queue.get()
if msg is ActorExit:
raise ActorExit()
return msg
def stop(self):
if self._thread.is_alive():
self.send(ActorExit)
def start(self):
if not self._thread.is_alive():
self._thread = Thread(target=self._bootstrap)
self._thread.start()
def _bootstrap(self):
try:
self.run()
except ActorExit:
pass
def run(self):
while True:
msg = self.receive()
print(msg)
# Sample Actor
class PrintActor(Actor):
def run(self):
while True:
msg = self.receive()
print("Got:", msg)
if __name__ == '__main__':
p = PrintActor()
p.start()
p.send("Hello")
p.send("World")
p.stop()
這里我們定義了Actor模式的基類Actor和其子類實現(xiàn)PrintActor。
Actor對象是一個計算單元,所以每個actor都需要包含了一個Thread對象,用以執(zhí)行給定的消息處理操作。同時actor對象也包含了一個Queue對象,實現(xiàn)了異步調用(在send消息后能立刻返回控制權)。
在啟動actor之后,線程對象會執(zhí)行引導函數(shù)_bootstrap,進而執(zhí)行run函數(shù)。一般來說,在run函數(shù)里,我們會通過receive函數(shù)來讀取之前通過send函數(shù)發(fā)送來的消息,然后根據給定的邏輯處理消息。特別地,receive函數(shù)會拋出ActorExit異常,而ActorExit異常正是我們作為停止線程的哨兵值。
Actor model 作為一種并發(fā)運算模型,其作用在于將 目的(做什么)和時機(何時做)解耦,以優(yōu)化應用程序的吞吐量和結構。例如用戶想要打印一段信息,那么他需要去調用對應的PrintActor的send函數(shù)(目的),然后程序會立刻返回,繼續(xù)執(zhí)行操作;至于這個打印的需求何時(時機)用戶則無需關心。而這也是Actor模式吸引人之處——簡單性:用戶設計時,只需在其子類中定義好 run函數(shù)中的處理操作,使用時只需調用send函數(shù),消息就能得到對應的處理。
抽象到一個新的高度,我們可以看到Actor模式在設計上有3個并發(fā)防御原則:
- 單一權責:每個actor只負責處理對應類型的消息,且其中的并發(fā)代碼和其他組件的代碼是分隔開的。
- 限制數(shù)據成員作用域:actor只開放
send接口,最大程度地減小內部成員的暴露,限制可能被共享的數(shù)據的訪問,縮減“臨界區(qū)”范圍。 - 線程之間獨立:每個actor線程都只在自己的世界里存在,不與其他線程共享數(shù)據。線程之間唯一的交互是
send出去的消息,所以,當消息是不可變對象 或者 actor存儲的是消息副本時,就能保證兩個線程之間是獨立的。
妥善使用Actor模式,保證子類也符合這三個原則,那么程序在架構層面上,能很好地降低多線程應用程序設計的復雜度。
最后,我們發(fā)散一下思維。
這里PrintActor是最簡單的一種Actor,實踐中我們可以設計一個DatabaseActor,用來存儲數(shù)據;設計一個LogActor,用來記錄日志;甚至可以把send方法實現(xiàn)為在socket上的數(shù)據傳輸,將“發(fā)送信息”這一概念擴展到多進程甚至是分布式系統(tǒng)之中。
參考書籍:
- 《Python Cookbook》第三版
- 《代碼整潔之道》Robert C.Martin