系統中有多個任務同時存在稱之爲“併發”,併發設計已然成爲大規模集羣框架的必要特徵,本文簡單的介紹Scala和golang的併發模型的設計,重點在於比較Scala和Golang在併發實現上的差異。

一、Scala和Golang的併發實現原理

Scala語言併發設計採用Actor模型,採用的是Akka Actor模型庫,主要特徵如下:

  • “一切皆是參與者”,且各個actor間是獨立的;
  • 發送者與已發送消息間解耦,這是Actor模型顯著特點,據此實現異步通信;
  • actor是封裝狀態和行爲的對象,通過消息交換進行相互通信,交換的消息存放在接收方的郵箱中;
  • actor可以有父子關係,父actor可以監管子actor,子actor唯一的監管者就是父actor;
  • 一個actor就是一個容器,它包含了狀態、行爲、一個郵箱(郵箱用來接受消息)、子actor和一個監管策略;
    Go語言與其他語言不同的是,它從語言層面上就支持了併發,Go更提倡“以通信來共享內存,而非以共享內存來通信”,主要特徵如下:
  • goroutine(協程,Go的輕量級線程)是Go的輕量級線程管理機制;
  • 通過管道(channel)來存放消息,channel在goroutine之間傳遞消息,可以緩存;

二、Scala和Golang在併發實現模型上的主要差異:

  • actor是異步的,因爲發送者與已發送消息間實現瞭解耦;而channel則是某種意義上的同步,比如channel的讀寫是有關係的,期間會依賴對方來決定是否阻塞自己;
  • actor是一個容器,使用actorOf來創建Actor實例時,也就意味着需指定具體Actor實例,即指定哪個actor在執行任務,該actor必然要有“身份”標識,否則怎麼指定呢?!而channel通常是匿名的, 任務放進channel之後你不用關心是哪個channel在執行任務;

三、具體實現

說明:對1-10000的整數進行累加,把1-10000平均劃分爲四部分,啓動四個線程進行併發計算,之後將四個線程的運行結果相加得最終得累加值。

Scala實現

Scala定義兩個Actor實例,MasterActor和SumActor,前者是後者的父actor,父actor可以監管子actor,子actor唯一的監管者就是父actor。

val sumActor = context.actorOf(Props[SumActor]
                  .withRouter(RoundRobinPool(nrOfInstances = 4)), name = "sumActor")

主程序向MasterActor發送計算指令,然後在線程池中初始化子actor,一共有四個SumActor參與併發計算。

啓動計算actor,每個計算線程都會調用計算方法,該方法將處理分段的整數累加,並返回分段累加值給父actor MasterActor。

在整個運算過程中,我們很容易理解發送者與已發送消息間的解耦特徵,發送者和接受者各種關心自己要處理的任務即可,比如狀態和行爲處理、發送的時機與內容、接收消息的時機與內容等。我們可以將更多的精力放在設計上,將各個actor的功能及關聯關係表述清楚,剩餘的代碼實現就非常容易。

Golang實現

Go語言的實現併發功能主要依賴goroutine和channel,首先創建一個計數用的channel,隨後發起四個併發計算的協程,

每個計算協程調用計算方法進行分段計算,分段計算完成之後,都會往channel中傳入一個計數標誌,在率先進入的計數標誌沒有被讀取之前,其他協程在計算結束之後,不能往channel中加入計數標誌,只能等待,如此往復,直到channel中的所有計數標誌全被取出,意味着所有協程全部處理完畢,在所有的分段計算結束後,就可以計算總的累加值

四、對比Scala和Golang

  • 拿Scala和Go的併發效率來對比,應該是沒什麼意義的,其間要受到各自內部實現、類型系統、內存使用機制、併發模式、併發規模以及硬件支持等等複雜因素的影響。
  • Akka的actor是解耦的、相對獨立的,定義好各個actor間如何溝通,剩下的東西就儘管交給它們處理好了,它們自會按既定方式各司其職,而且每個actor“麻雀雖小五臟俱全”,這也是其解耦性做得好的必然基礎。Go語言則獨闢蹊徑,通過goroutine和channel,以更輕量級的協程方式來處理併發,雖說是更輕量,但你仍得花點心思關注下channel的狀態,別一不小心阻塞了,特別是channel數量多邏輯複雜的情況。
  • Akka的Actor實現是庫的形式,其也能應用於Java語言。Go語言內嵌了協程的併發實現。
  • Akka基於JVM,實現模式是面向對象,天然講究抽象與封裝,雖然可以穿插混合應用函數式風格。而Go語言顯然體現了命令式語言的風格,在需要考慮封裝性的時候,需要開發者多着墨。
  • Go語言中輕量級的協程可以輕易啓動數十上百萬個,這對Scala來說當然是有壓力的。但相較而言,Go語言的普及及應用程度尚遠不及Java生態,我也希望更多的應用能夠實踐Go語言。此外,從代碼簡潔程度來看,Go語言應該會更簡潔些吧。
相關文章