Actor模型淺析 一致性和隔離性

zzzzMing發表於2018-11-16

一.Actor模型介紹

在單核 CPU 發展已經達到一個瓶頸的今天,要增加硬體的速度更多的是增加 CPU 核的數目。而針對這種情況,要使我們的程式執行效率提高,那麼也應該從併發方面入手。傳統的多執行緒方法又極其容易出現 Bug 而難以維護,不過別擔心,今天將要介紹另一種併發的模式能一定程度解決這些問題,那就是 Actor 模型。

Actor 模型其實就是定義一組規則,這些規則規定了一組系統中各個模組如何互動及回應。在一個 Actor 系統中,Actor 是最小的單元模組,系統由多個 Actor 組成。每個 Actor 有兩個東西,一個是 mailbox,一個是自身狀態。同時 Actor 有接收和傳送的功能。下面程式碼給出一個大概的 Actor 樣例:

trait Actor {
  //持有一個表示自身狀態的私有變數
  val state:Integer = 0;
  //持有一個mailbox 的佇列
  val mailBox:mutable.Queue[Message] = scala.collection.mutable.Queue[Message]()
  def send(message : Message): Unit ={
      ...
  }
  def recive(): Unit ={
      ...
  }
}

當一個 Actor 接收到訊息後,它會執行下面三種操作中的一種:

  • 建立其他 Actor。
  • 向其他 Actor 傳送訊息。
  • 修改自身狀態。
    需要注意的是,儘管許多 Actor 同時執行,但是一個actor只能順序地處理訊息。也就是說其它 Actor 傳送了三條訊息給一個 Actor ,這個 Actor 只能一次處理一條。所以如果你要並行處理3條訊息,你需要把這條訊息發給3個actors。

下面這張圖展示了一個簡單的 Actor 模型系統:

Actor模型淺析 一致性和隔離性

瞭解了 Actor 模型的大概規則後,我們用兩個具體的例子來看看 Actor 模型的妙處以及不足吧。

二. 兩個例子

2.1 素數計算

假設我們現在有一個任務,需要找出100000以內素數個數,並且使用多執行緒的方式實現。

下圖展示了使用共享記憶體的方式和以Actor模型的方式進行併發執行。
Actor 素數計算

這裡展示了兩種處理併發的不同思路,傳統的方式是通過鎖/同步的方式來實現併發,每次同步獲取當前值,並讓一個執行緒去判斷值是否為素數,是的話再通過同步的方式對計數器加1(這裡的說明只是作為提供思路用,這種方法自然有很大的優化空間)。

而使用 Actor 模型則不一樣,它將這一過程拆分成幾個模組,即拆分成幾個 Actor 。每個 Actor 負責不同的部分,通過訊息傳遞的方式讓這幾個 Actor 協同工作,並且其中涉及到主要計算的 Actor 可以有多個,通過多個 Actor 協同工作實現併發。

2.2 銀行轉賬

銀行轉賬的任務描述很簡單,假設有兩個使用者,現在使用者A向使用者B轉賬100元,這個 Actor 模型該如何設計呢?

使用者 A 和 使用者 B 明顯是兩個 Actor ,但我們同時還需要一個可以控制使用者A Actor 和使用者B Actor 的 Actor ,我們稱之為 轉賬管家 Actor。那麼流程圖如下。
Actor 銀行轉賬

可以看到,當一個轉賬需求過來的時候,Actor 管家會先向 使用者A Actor 傳送扣款 100 元的資訊,接受到扣款成功訊息後再傳送訊息給使用者B Actor,傳送讓其增加 100 元的訊息。

一切看起來都很美好是吧,但這裡面有一個問題,那就是在使用者A Actor 扣款期間,使用者B Actor 是不受限制的,此時對使用者B Actor 進行操作是合法的!針對這種情況單純的Actor模型就顯得比較乏力了,需要加入其他機制以保證一致性。

看到這你就明白了,Actor 模型並非萬能的,它有一定的缺點。那就是針對一致性要求比較強的場景比較乏力。

三. 為什麼會出現 Actor 模型

接下來我們來聊聊為什麼會有 Actor 模型這種併發程式設計模型出現。

我們需要先說說併發性中的一致性隔離性

一致性即讓資料保持一致,比如銀行轉賬例子中,使用者A 轉給 使用者B 100塊錢,沒有其他干擾的情況下,轉賬完成時。使用者A 的賬戶必然減少 100 元,使用者B 的賬戶必然增加100 元,這就滿足了一致性。不能說使用者A 減少50 或使用者B 增加了 200。

隔離性可以理解為犧牲一部分的一致性需求,而獲得效能的提高。打個比方,在完全一致的情況下,任務都是序列的,這時候也就不存在隔離性了。

明白這些之後,你就直到為什麼會有 Actor 模型了。

傳統併發模式,共享記憶體是傾向於強一致性弱隔離性的。比如悲觀鎖/同步的方式,其實就是使用強一致性的方式控制併發。而 Actor 模型天然是強隔離性且弱一致性,所以 Actor 模型在併發中有良好的效能,且易於控制和管理。

這樣你就明白 Actor 模型適合於什麼樣的併發場景了,當對一致性需求不是很高的情況下且對效能需求較高時,Actor 模型無疑是一個值得嘗試的方案。


推薦閱讀 :
從分治演算法到 MapReduce
Actor併發程式設計模型淺析
大資料儲存的進化史 –從 RAID 到 Hadoop Hdfs

相關文章