為什麼我覺得 Actor 很難用?
這幾天對Actor有所理解
反正就是得出了一個結論,有些問題的解決方案,足夠面向對象+分散式後,就變成了Actor回想過去,自己也實現過Actor,只是不知道那是Actor,僅僅是按著自己的思路解決了問題會發這個問題,大概是我還在和那個面試官賭氣,對不起==============================原值===============================
知道 Actor 是因為一次面試,我對面試官說「解決並發問題的唯一手段是局部串列」,面試官說還可以用Actor解決,因為我不了解這個並發模型,結果被pass掉了。我查閱了很多Actor的資料,也用Scala寫了Demo,給我的感覺就是同樣的東西用Thread寫起來輕鬆多了。大部分Actor的介紹都會強調其避免了同步和鎖,可我寫java的多線程程序不用同步和鎖很多年了,如何避免使用同步和鎖實現線程安全也是我面試別人必問的問題。上面的避免指的是:在自己寫的代碼里避免了同步和鎖,並不是底層不用在自己寫的代碼里避免了同步和鎖,並不是底層不用
在自己寫的代碼里避免了同步和鎖,並不是底層不用咱說三遍你們能理解了么?而且一般介紹並發模型的書里,同步和鎖跟Actor用的是不同的例子!而同步和鎖的場景用Actor根本無法實現!我到現在也不明白用Actor如何解決競爭資源的問題(實際上我認為根本做不到)最後說到函數編程、無可變狀態,用不用Actor不是也都可以做到么?我現在開發java程序已經是遵循這樣的原則了。
Actor模型本質上是計算模型,是抽象度很高的模型,不要和具體的技術搞混了。Actor模型的類比應該是圖靈機和Lambda-calculus之類的。
稍微解釋一下,我們現在說的Actor模型,指的應該是Gul Agha(現在在UIUC)在1985年的論文:Actors: A Model of Concurrent Computing in Distributed Systems里使用的Actor模型(被 @bhuztez 指出Actor模型是Carl Hewitt提出的,查了一下,這個模型一開始是Carl Hewitt等人在1973年提出的,直到1985年Gul Agha在他的博士論文里發展了transition-based semantic model,完善了Actor Model Theory,那麼我們就按照這篇論文里的Actor模型來好了)。這個模型在一個比具體的programming lanugage更抽象的層面上,論文里說"abstract machine",或者說computation model,去定義一個computational element的本質。這是一個computation的模型,不應該拿它和一些具體的技術來進行比較(比如thread)。
論文里提出,可用於計算的本質元素包括:Sequential Processes,Functions transforming data values,和Actor。Sequential Processes是類似於狀態機的概念,是從C語言開始的大多數人對編程語言的理解,一個程序是一系列狀態的改變,這裡面是允許做並行的計算的(一般是共享內存),但是需要仔細考慮順序和狀態的變化;Functions transforming data values就是基於lambda-calculus的函數式模型,由於function中間不含狀態,簡化了一部分並行的難度,但是由於現實世界有些情況是必須基於歷史的,模型里有用feedback之類的來處理;所謂Actor模型是Sequential Processes和Functions transforming data values兩者的結合,可以理解為是綜合了過程式計算和函數式計算的一個計算模型。一個Actor會把接收到的消息映射為三個部分,傳給其他Actor的消息,一個新的行為(用來處理下一個消息),和創造一些新的Actors。這個模型的好處在於它既有函數式模型里的隔離的概念,因為對別的Actor來說,Actor內部的狀態改變並不會影響他們自己,所以對一個Actor來說,別的Actor就好像不存在狀態一樣;但是另一方面,對每個Actor自己來說,它是有「歷史」的,這是Sequential Processes模型里的概念。Actor模型的強大之處在於它是可以完全表達出任何Sequential Processes或者Functions transforming data values的系統的,但是反之則不一定能表達出來。事實上現代的分散式系統,其實或多或少都借鑒了一些Actor模型,比如著名的RPC機制。基本上如果你把一個進程的概念放進一個分散式系統里,這個進程如果在這個分散式系統里,不是必須在本地機器調用的話,我覺得這個進程事實上就是一個Actor了。因為跨了不同的機器,根本沒法使用共享內存,肯定是傳遞消息。
看到這裡我們大概會有個概念,Actor模型主要其實不是針對並行計算的,最主要的還是在分散式計算裡面。當然,即使在並行計算里,採用Actor模型也是有好處的,最主要的是比較起共享內存的並行方式,Actor模型會提供一定程度上的抽象,減少一些細節。或者說,如果是在單機的並行計算里,Actor模型就比較近似於函數式編程了。至於同步和鎖........我覺得這個跟Actor並不矛盾啊.......Actor總體上是個非同步的模型,Actor之間也沒有共享內存,但是同步是可以構建在非同步之上的啊,如果需要同步也可以做的,Actor之間雖然沒有共享內存,但是Actor自己也可以加鎖咯,不過這樣似乎沒啥意義......
函數式編程,不可變狀態,用不用Actor確實都可以做到,因為Actor本來就是吸收了函數式的一部分特徵做出的計算模型。但是如果考慮在分散式環境下,如果有很多台計算機的時候,Actor模型可以提供的抽象能力就要更強了,它可以在語言層面上提供對分散式的支持。但是這種情況下用Actor模型表達出的系統就很難直接用函數式或者過程式表達出來,往往要提供其他的工具作為支持。
原始的論文在這裡:https://www.cypherpunks.to/erights/history/actors/AITR-844.pdf
並非研究此方向的,希望有大牛指教下。
------------------------------------------------------------------------------------------
原本以為差不多明白Actor模型是什麼東西了,仔細研究了一下之後發現其實跟我想的還有很多地方都不一樣,根據看到的一些論文的描述,Actor模型現在用圖靈機模型(也就是我們平時用的計算機)模擬其實會非常低效的(其實沒有深入看,不太明白為什麼),而且對於非決定性問題可能會出現問題(也沒有太看懂)。所以總體來說,Actor模型也只是個理論模型,真正完整的實現基本上就沒有...........不過根據Actor模型實現的各種變種的編程語言和框架還是蠻多的,也對分散式系統的研究有一定指導意義~世界是複雜的,物理學都沒出現大一統的理論,相對論和量子力學都還互不兼容呢,怎麼能指望一個actor就解決所有問題了,面向對象也好,函數也罷,都只是描述世界的一個角度罷了,一個描述自然的模型。
然而可悲的是,人類對自然的認識永遠只能無限的接近真理,卻永遠無法探究到所謂的本源,因為人的經驗是有限的,所以認識自然的過程其實都是在盲人摸象。從宗教到物理學,可以不斷接近,卻永遠無法做不到睜眼一窺全貌,區區一個編程模型何德何能?敢妄自尊為解決一切的銀彈?
從實際出發,不同問題用不同方法,一個模型是否可靠,看的從來不是理論是否高明,檢驗真理的唯一標準只有一條,就是實踐,自己動手去嘗試證實。不明白現在的人為何喜歡抱住某根大腿就以為自己抓住了全世界所有人一般的興奮?這不和抱住象腿的瞎子以為大象是根柱子一樣可笑么?
----好吧,照問題答一下,只要有資源競爭,鎖是逃不掉的,你不調用不代表不存在,具體到actor裡面,mailbox就是一個加鎖的東西。如果你的不同actor只見要處理相同的對象,比如所有actor都在訪問修改一個HashMap,你照樣需要加鎖。你用了 java.util.concurrent 也很難避免所,比如你要確保正確的使用:判斷某個人錢是否夠100元,夠的話扣100元並增加體力。每個人的錢和體力都是保存在 ConcurrentHashMap 中,單獨操作沒問題,但是你要保證上面這個邏輯的正確性,就只能用鎖。
所謂軟體 STM 的方式,lock free來實現的無鎖容器,後面用到的 compare and swap 指令,它也是要加鎖的,比如x86的 cmpxchg 來做 CAS前面都要把匯流排給鎖了,只是說衝突時不會切到內核態罷了。不通過具體代碼,而是通過各種名詞的技術討論,都是浪費時間。scala實現的actor、erlang實現的actor、論文里的actor、作為設計模式的actor,看似一個東西,但實際都在各說各的,真無聊。
知道Actor是因為一次面試,我對面試官說「解決並發問題的唯一手段是局部串列」,面試官說還可以用Actor解決,因為我不了解這個並發模型,結果被pass掉了。
相對整個系統的並發,actor 就是 「局部串列」,mailbox 裡面的 messages 都是順序執行的。「解決並發問題的唯一手段是局部串列」,你要 「局部串列」 ?怎麼弄 ?最終還得用鎖。
大部分Actor的介紹都會強調其避免了同步和鎖,可我寫java的多線程程序不用同步和鎖很多年了,如何避免使用同步和鎖實現線程安全也是我面試別人必問的問題。
我有點被這句話嚇到了。erlang,akka
提供給上層程序的 API 是不需要鎖的,但他們的底層實現不可避免的會用到鎖。複雜點的並發程序是避免不了鎖的,只是鎖在哪一層的問題 ?erlang 把鎖限制在語言層,akka 把鎖實現在框架層,你用其他語言(框架)寫程序的時候怎麼辦 ?鎖放哪裡 ?
Locks, Actors, And STM In Pictures
一般來說,更抽象的模型都更容易理解,也更符合使用的場景。而且由於避免了許多靠近底層的細節,所以代碼量要少很多。
但是總有些人拒絕進步,喜歡用底層來做一個既不容易理解,也不健壯的程序。這個世界上明明有挖掘機,有人說挖掘機不能適用所有場景,學習成本太高,用挖掘機挖出的坑只能用挖掘機維護,而且挖掘機本身還不免費,我們還是用鎬和鐵杴吧。
世界總是進步的,進步在軟體開發行業的體現其實就是更抽象的模型,更高級的方法,更先進的工具。底層的細節就在那裡,但是掌握了底層細節,永遠也做不出一個多快好省的項目。看到一個個技術分析的答案我也是醉了。這事明顯是面試官聽到題主說【唯一手段】後,覺得題主天真,不想收留題主,而故意隨便找個借口比如Actor來攆走題主而已,還都當真了......
我看了這麼多覺得就是同一件事情的不同表述方式。
如果從純理論的角度,actor可以默認我有消息隊列的機制。但除非從硬體層就是這樣的機制,否則這種同步隊列mailbox必然用到鎖(或者進一步,硬體鎖也是鎖啊),這就是局部串列啊,unix里也有pipe啊。換句話說,我覺得actor就是造出來的用來圈錢攢paper的概念,新瓶裝舊酒而已,不用太在意。只是沒掌握所以覺得難用罷了
首先actor其實是更底層的抽象,被認為是比線程和鎖模型更簡單易理解易使用的抽象,來取代後面這種模型。1、actor是最原子的計算單元。2、actor有自己的狀態。3、actor之間通過消息通信,scala和java的actor框架akka提供的方式有tell and forget,ask with future這樣的方式。4、actor通過消息觸發改變自己內部狀態。但是就scala和java來說jvm在框架之外本身底層還是線程在驅動,依舊還是需要了解這些知識方便調優。所以模型上來說actor只會自己搞自己。我覺得題主糾結原因是還是用線程的模型去思考actor會導致很困惑。
對actor 不是很了解,但是看到同步和鎖的問題,想表達下個人觀點;我理解樓主這裡說的同步和鎖,是代碼邏輯層所說的鎖,在一定程度上,這種鎖是可以避免的,比如disruptor框架,就是藉助cpu指令級別的原子操作CAS實現無鎖隊列,不是說CAS就沒有鎖,而是cpu級別的鎖和代碼邏輯層面的鎖是完全不同的兩種意義。。。,不知道我理解的對不對
上面答案說了很多了。
actor是一種思想,akka或erlang process只是對應的實現方式。
當你的非同步事件足夠多的時候,你便會發現這種模式是個解決這類問題的非常好的辦法之一。同時一般你還要考慮容錯,分散式通信等問題,像akka這種框架提供了比較完整的解決方案。你如果非要把整個本來就同步的系統變成actor方式,自然會覺得彆扭。不好用只能說可能你還沒遇到適合的場景,以及你還不夠上手。話說Actor不也是局部串列么
其實,你沒被錄用,不是因為不知道Actor,而你在溝通中的態度。大家都在說,Actor解決競爭資源問題,就算你不懂,你也應該先去弄清楚它是什麼(別說你弄不清楚,弄不清楚,你還真不該干這行。),再來談你的看法。可你是直接先入為主了,這樣的態度,在工作中別人就很難跟你溝通了。編程是一門技術,但更多的是一種協作。
回到正題,解決競爭資源的思路有兩種,第一種就是常規的加鎖,目的是讓大家輪流訪問,也就是你所說的【局部資源串列訪問】,這種方式的問題在於大量並發環境下,鎖會由於競爭帶來大量的線程切換開銷,就是說一個線程被系統調度起來後,遇到鎖就必須放棄剩下的時間片。在極度惡化的情況下,系統的多數CPU資源都用來喚醒線程再放棄時間片了。解決這個問題方式之一就是,盡量減少加鎖的持續時間,也就是鎖資源,不鎖過程或行為。但是不鎖過程、行為,又與面向對象和面向過程追求函數式編程的模式有衝突,難以實現函數(行為)復用。尤其是一個行為要求同時鎖定多個資源時,極易發生死鎖。。。所以,鎖的解決方案,屬於並發中的低級手段,解決低級資源並發訪問問題。
第二種思路,就是Actor,資源複製、行為獨立,既然你這個行為需要訪問這些資源,就把這些資源打包複製給你,給你一個獨立的環境自己玩。也就是通過數據複製來解說行為訪問資源的衝突。這種模式的缺點是有資源複製的額外開銷,在小並發環境下,效率不如鎖。。。。但是,在大並發環境下,Actor 實現獨立的行為邏輯,所有Actor 通過非同步的消息溝通,這實際上就是併發模式下的面向對象思維,只是對象成了Actor。相比面向對象的函數過程調用,消息收發效率要低很多,但是考慮Actor實際上等價於大粒度的對象群,這樣的效率低下是可接受的。簡單說,如果我們把所有的面向都一刀切變成Actor模式,那系統會慢的無法忍受;我們只是根據並發行為的需要,在更高的層面將若干對象的功能封裝成Actor模式,則既能完美實現面向對象的行為封裝,又能實現並發協作,最後性能還在可接受的範圍。
再來談談真正的無鎖編程,利用某些特殊的容器特性(而且可能會在多核下失效),我記得有個環形緩衝區的例子,的確可以做到無鎖並發操作。但這種編程模式只能用於某些特殊結構的容器,不具有普遍性。屬於雕蟲小技。看你在其他回答下的評論,你說的無鎖編程,調用的API封裝了鎖操作。你這裡說的無鎖編程概念,和我們行內的概念是不一致的,因為我們行內討論無鎖編程,是為了分析性能、鎖衝突的代價,API封裝了鎖操作,這個鎖操作仍然會引發競爭性能下降,作為程序員,對你而言這是有鎖編程,因為你要關注性能和潛在的死鎖風險。就好像我們說Actor可以解決並發訪問問題,但實際上Actor模式是依賴鎖的,Actor模式的消息隊列操作是一定要用鎖的,只是這個鎖只是鎖隊列的壓入和彈出操作,發生線程進入鎖還未退出時就被系統切換時間片的概率極小,性能惡化風險幾乎為零。其實,你可以做個試驗,讓一個線程不斷的循環只做隊列操作,這時,就會出現性能惡化,因為這個線程大部分時間片都消耗在鎖的內部,這樣就會頻繁發生線程切換後,其他線程進不了這個鎖的情況。
看到問題修改歷史,簡直low得不行。原來所謂無鎖並發,就是上層代碼沒有同步語義,不用去管框架和庫呀。我就這個表情。
還面別人呢,按你這個邏輯,我只需要在庫裡面封裝這樣的工具方法。
static void lockFree(Runnable r)
{
synchronized (Object.class)
{
r.run();
}
}
然後所有調用都進裡面走一圈,不就行了嘛。
或者我提供個這樣的抽象類。神器啊!繼承即可實現「無鎖」並發哦!
abstract class LockFreeThread extends Thread
{
public void run()
{
synchronized (LockFreeThread.class)
{
exec();
}
}
abstract void exec();
}
看了半天,好像就是用消息隊列將並發操作變成單線程操作。理論上每個線程佔用的資源不衝突的話當然可以不用鎖,只要保證消息隊列讀寫不出錯就行
請問有哪些同步和鎖的使用場景是Actor無法實現的?
按知乎流行語:先問是不是…
比如競爭資源問題:資源由Actor管理,其他的Actor競爭使用,則管理的Actor有仲裁權。最近也在看Actor的東西,目前感覺難用。覺得是因為Actor只限定了思想抽象但並未提供實現技術指導,所以在使用上較虛不務實。並且在開發上,無共享資源的並發問題Actor優勢並不明顯(除了不用自己管理線程,所謂代碼風格良好和編碼速度快),而需要共享資源的問題Actor也不符合需求了。
對答主現在所說應用了Actor解決的問題比較好奇,能否分享。
actor 是 幾種並發模型之一 ,actor csp 都是讓比 傳統的線程 鎖 更可控 當然都有各自的問題scala erlang 都只是用其思想 實現還是個不相同的
另外
actor 才是真正的oop 好不好用需要看場景ps 我喜歡actor 函數式風格出現資源競爭的情況就是出現了多線程執行actor,這違背actor編碼原則,需要fork children actor。merge的時候由於還是單線程所以不會出現RC
推薦閱讀: