[8] - Actor 與并發(fā)

Actor 是 Scala 基于消息傳遞的并發(fā)模型,雖然自 Scala-2.10 其默認(rèn)并發(fā)模型的地位已被 Akka 取代亩冬,但這種與傳統(tǒng) Java硼身、C++完全不一樣的并發(fā)模型依舊值得學(xué)習(xí)。

如何使用 Actor

擴(kuò)展 Actor

先來(lái)看看第一種用法营袜,下面是一個(gè)簡(jiǎn)單例子及部分說(shuō)明

//< 擴(kuò)展超類 Actor
class ActorItem extends Actor {
  //< 重載 act 方法
  def act(): Unit = {
    //< receive 從消息隊(duì)列 mailbox 中去一條消息并處理
    receive { case msg => println(msg) }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    //< 啟動(dòng)
    actorItem.start()

    //< 向 item 發(fā)送消息
    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}
輸出:
actor test1

這種用法在實(shí)際中并不常用丑罪,需要:

  1. 擴(kuò)展超類 Actor
  2. 重載 act 方法
  3. 調(diào)用擴(kuò)展類對(duì)象 start 方法

使用 scala.actors.Actor.actor 方法

第二種方式是實(shí)際中常用并且是 Scala 社區(qū)推薦的吩屹,例子如下:

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = actor {
      receive { case msg => println(msg) }
    }

    //< 向 item 發(fā)送消息
    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}
輸出:
actor test1

這里需要特別注意的是,actor 其實(shí)是scala.actors.Actor的 actor 方法免绿,并不是 scala 語(yǔ)言內(nèi)建的擦盾。
這種使用方法更加方便,與第一種擴(kuò)展超類 Actor 有以下幾點(diǎn)不同:

  1. 使用 Actor.actor 方法(返回類型為Actor)而不是擴(kuò)展 Actor 并重載 act 方法
  2. 構(gòu)造完成即啟動(dòng)厌衙,不需要調(diào)用 start方法(當(dāng)然你調(diào)用了也不會(huì)有什么問(wèn)題)

使用 react

除了可以使用 receive 從消息隊(duì)列 mailbox 中取出消息并處理婶希,react 同樣可以蓬衡。receive 和 react 的區(qū)別與聯(lián)系將在下文中說(shuō)明。先來(lái)看看怎么用筒饰,其實(shí)只要把上面兩段代碼的 receive 替換成 react 即可:

class ActorItem extends Actor {
  def act(): Unit = {
    react { case msg => println(msg) }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}
輸出:
actor test1

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = actor {
      react { case msg => println(msg) }
    }

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}
輸出:
actor test1

持續(xù)處理消息

如果你仔細(xì)觀察壁晒,就會(huì)發(fā)現(xiàn)上面的每個(gè)示例中,都向 actor 發(fā)送了"actor test1"和"actor test2"兩條消息谬晕,但最終只打印了"actor test1"這一條消息。這是因?yàn)榘锟祝还苁?receive 還是 react不撑,都只從 mailbox 中取一條消息進(jìn)行處理,處理完之后不會(huì)再取一條處理姆坚。如果想要持續(xù)從 maibox 中取消息并處理揩页,也有兩種方式。

方式一:使用 loop萍程。適用于擴(kuò)展 Actor 和 actor 方法兩種方式

class ActorItem extends Actor {
  def act(): Unit = {
    loop {
      react { case msg => println(msg) }
    }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}
輸出:
actor test1
actor test2

方式二:在 receive 處理中調(diào)用receive兔仰;在 react 處理中調(diào)用 react。僅適用于 actor 方法這種方法

class ActorItem extends Actor {
  def act(): Unit = {
    react {
      case msg => {
        println(msg)
        act()
      }
    }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}

Actor是如何工作的

每個(gè)actor對(duì)象都有一個(gè) mailbox忍法,可以簡(jiǎn)單的認(rèn)為是一個(gè)隊(duì)列饿序,用來(lái)存放發(fā)送給這個(gè)actor的消息羹蚣。
當(dāng) actor 發(fā)送消息時(shí),它并不會(huì)阻塞咽弦,而當(dāng) actor 接收消息時(shí)胁出,它也不會(huì)被打斷。發(fā)送的消息在接收 actor 的 mailbox 中等待處理全蝶,直到 actor 調(diào)用 receive 方法。

receive 具體是怎么工作的呢嫂用?來(lái)看看它的源碼:

def receive[R](f: PartialFunction[Any, R]): R = {
  var done = false
  while (!done) {
    //< 從 mailbox 中取出一條消息
    val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => {
      senders = replyTo :: senders
      //< 與偏函數(shù)進(jìn)行匹配嘱函,匹配失敗返回 null
      val matches = f.isDefinedAt(m)
      senders = senders.tail
      matches
    })
    if (null eq qel) {
      //< 如果當(dāng)前mailbox里面沒(méi)有可以處理的消息,調(diào)用suspendActor往弓,該方法會(huì)調(diào)用wait
      waitingFor = f.isDefinedAt  
      isSuspended = true 
      suspendActor()  
    } else {
      //< 執(zhí)行到這里就說(shuō)明成功從 mailbox 中獲得匹配的消息
      received = Some(qel.msg)
      senders = qel.session :: senders
      done = true
    }
  }

  //< 成功獲得消息后函似,調(diào)用 f.apply 來(lái)執(zhí)行對(duì)應(yīng)的操作
  val result = f(received.get)
  received = None
  senders = senders.tail
  result
}

一圖勝千言,下圖為 receive 模型工作流程

actor_receive.jpg

與線程的關(guān)系

Actor 的線程模型可以這樣理解:在一個(gè)進(jìn)程中顿天,所有的 actor 共享一個(gè)線程池蔑担,總的線程個(gè)數(shù)可以配置,也可以根據(jù) CPU 個(gè)數(shù)決定鸟缕。

當(dāng)一個(gè) actor 啟動(dòng)后排抬,Scala 分配一個(gè)線程給它使用,如果使用 receive 模型番甩,這個(gè)線程就一直為該 Actor 所有届搁。
如果使用 react 模型,react 找到并處理消息后并不返回,它的返回類型為 Nothing么翰,Scala 執(zhí)行完 react 方法后辽旋,拋出異常檐迟,調(diào)用 act 也就是間接調(diào)用 react 的線程會(huì)捕獲這個(gè)異常码耐,忘掉這個(gè) actor,該線程就可以被其他actor 使用敦间。
所以束铭,如果能用 react 就盡量使用 react,可以節(jié)省線程带猴。

良好的 Actor 風(fēng)格

只通過(guò)消息與 actor 通信

舉個(gè)例子懈万,一個(gè) GoodActor可能會(huì)在發(fā)往 BadActor 的消息中包含一個(gè)指向自己的引用,來(lái)表明作為消息源的自己口予。如果 BadActor 調(diào)用了 GoodActor 的某個(gè)任意的方法而不是通過(guò) "!" 發(fā)送消息的話渴语,問(wèn)題就來(lái)了。被調(diào)用的方法可能讀到 GoodActor 的私有實(shí)例數(shù)據(jù)牙甫,而這些數(shù)據(jù)可能是由另一個(gè)線程寫(xiě)進(jìn)去调违。結(jié)果是,你需要確保 BadActor 線程對(duì)這些實(shí)例數(shù)據(jù)的讀取和 GoodActor 線程對(duì)這些數(shù)據(jù)的寫(xiě)入是同步在一個(gè)鎖上的且轨。一旦繞開(kāi)了 actor 之間的消息傳遞機(jī)制虚婿,就回到了共享數(shù)據(jù)和鎖模型中。

優(yōu)選不可變的消息

由于 Scala 的 actor 模型提供了在每個(gè) actor 的 act 方法中的單線程環(huán)境至朗,不需要擔(dān)心在這個(gè)方法的實(shí)現(xiàn)中使用的對(duì)象是否是線程安全的剧浸。

確保消息對(duì)象是線程安全的最佳途徑是在消息中使用不可變對(duì)象。任何只有 val 字段且這些字段只引用到不可變對(duì)象的類的實(shí)例都是不可變的嫌变。

如果你發(fā)現(xiàn)自己有一個(gè)可變的對(duì)象腾啥,想繼續(xù)使用它,同時(shí)也想用消息發(fā)送給另一個(gè) actor碑宴,此時(shí)應(yīng)該考慮制作并發(fā)送它的一個(gè)副本,比如利用 clone 方法祸挪。

讓消息自包含

向某個(gè) actor 發(fā)送消息贞间,如果你想得到這個(gè) actor 的回復(fù)增热,可以在消息中包含自身。示例如下:

class ActorItem extends Actor {
  def act(): Unit = {
    react {
      case (name: String, actor: Actor) => {
        println( name )
        actor ! "Done"
      }
    }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! ("scala", self)

    receive {
      case msg => println( msg ) 
    }
  }
}
輸出:
scala
Done

使用樣本類

在上例中公黑,若把(name: String, actor: Actor)定義成類摄咆,代碼可讀性會(huì)大大提高

case class Info(name: String, actor: Actor)

class ActorItem extends Actor {
  def act(): Unit = {
    react {
      case Info => {
        println( Info.name )
        actor ! "Done"
      }
    }
  }
}

**傳送門: **Scala 在簡(jiǎn)書(shū)目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吭从,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谱醇,更是在濱河造成了極大的恐慌步做,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煮剧,死亡現(xiàn)場(chǎng)離奇詭異讼载,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)菇篡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門驱还,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)凸克,“玉大人,你說(shuō)我怎么就攤上這事萎战÷煳” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵蔚约,是天一觀的道長(zhǎng)涂籽。 經(jīng)常有香客問(wèn)我,道長(zhǎng)树枫,這世上最難降的妖魔是什么柳骄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任耐薯,我火速辦了婚禮,結(jié)果婚禮上体谒,老公的妹妹穿的比我還像新娘臼婆。我一直安慰自己,他們只是感情好故响,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著伪冰,像睡著了一般樟蠕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吓懈,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天靡狞,我揣著相機(jī)與錄音耍攘,去河邊找鬼。 笑死蕾各,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妨托。 我是一名探鬼主播吝羞,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钧排,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了符衔?” 一聲冷哼從身側(cè)響起糟袁,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤项戴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體界斜,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锄蹂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年水慨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敬扛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啥箭。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砌滞,靈堂內(nèi)的尸體忽然破棺而出坏怪,到底是詐尸還是另有隱情,我是刑警寧澤打掘,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布鹏秋,位于F島的核電站,受9級(jí)特大地震影響横朋,放射性物質(zhì)發(fā)生泄漏百拓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一祠够、第九天 我趴在偏房一處隱蔽的房頂上張望粪牲。 院中可真熱鬧,春花似錦落君、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至萌京,卻和暖如春雁歌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背知残。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工靠瞎, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人求妹。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓乏盐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親制恍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子父能,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 參考碼農(nóng)翻身 當(dāng)多線程并發(fā)遇到Actor goroutine, channel 和 CSP并發(fā)之痛 Thread,...
    合肥黑閱讀 2,951評(píng)論 0 5
  • 首先說(shuō)一下Actor Model净神,作為一種進(jìn)程或者線程間的通信模型,一般來(lái)說(shuō)有兩種選擇强挫,一種是CSP岔霸,比如Go語(yǔ)言...
    墨弈閱讀 3,382評(píng)論 0 51
  • 寫(xiě)在開(kāi)始 一般來(lái)說(shuō)有兩種策略用來(lái)在并發(fā)線程中進(jìn)行通信:共享數(shù)據(jù)和消息傳遞。使用共享數(shù)據(jù)方式的并發(fā)編程面臨的最大的一...
    架構(gòu)師修行之路閱讀 1,394評(píng)論 0 1
  • 讀《快學(xué)Scala 》一書(shū)的摘要 Scala 運(yùn)行于JVM之上俯渤,擁有海量類庫(kù)和工具呆细,兼顧函數(shù)式編程和面向?qū)ο蟆?在...
    abel_cao閱讀 1,282評(píng)論 0 8
  • 在使用Java進(jìn)行并發(fā)編程時(shí)需要特別的關(guān)注鎖和內(nèi)存原子性等一系列線程問(wèn)題,而Actor模型內(nèi)部的狀態(tài)由它自己維護(hù)即...
    北子萌閱讀 1,016評(píng)論 0 0