衆所周知我是π的支持者。緣由很簡單,就是π有着對『併發』概念的最小化完備表述。node
對於程序員來講我不推薦系統學習各類進程演算或者進程代數,根本上這是基於符號重寫的邏輯系統,跟編程的關係不大並且,對邏輯學基礎的要求過高了。程序員
但若是要一窺大師思想,我推薦閱讀Robin Milner的圖靈獎演講:Elements of interaction,這個網上一搜就有,並且很好讀;並且我相信它裏面把共享變量看做進程,把通信拆成I和O兩個部分的思想,都是你們能夠看懂的,並且,是醍醐灌頂的。編程
本文是在實現RP協議的原型時的一個思考。RP協議是一個相似HTTP Restful的通信協議,可是它不依賴HTTP,在必定程度上它更但願象網絡文件系統那樣完成通信雙方的交互,但操做語義上選擇了Restful的資源模型,而不是文件系統的open/read/write/close流語義。網絡
在π裏,最基礎的表達式之一是input prefix,c(x).P
這樣一個形式,它的意思是P
要等到channel c收到一個x消息纔開始運行,這很是接近事件驅動模式,實際上,事件和消息並無真正的區別,在數學上都同樣。併發
可是這裏須要注意的是,一旦x出現,這個表達式就被估值了,或者說,發生reduction,它就再也不存在了,至關於一個數學表達式計算完了。異步
這和另外一種狀況不同,就像一個庫函數,或者一個服務點,函數能夠被調用無數次,服務點能夠服務無數次,而不是調用一次或者服務一次就消失了;這個時候,在π裏,是用!
來表達的,這個符號叫作replication,意思是它後面的表達式有無數個,每次reduce一個以後還能夠繼續使用。異步編程
replication在編程中是普遍存在的,若是一個函數只能用一次你們都會以爲它沒什麼用了。函數
可是一個函數或者一個服務點是一次性消費掉的單例(如下稱爲sink),仍是replication,這裏區別很大。咱們舉個例子。學習
在RP協議裏,通信雙方的全部通信都是經過Message Passing完成的;在這種狀況下若是想模擬一個簡單的Request/Response:設計
Client在發出消息的時候要即時建立一個Path,也就是name,或者說channel,π意義上的channel,在π裏一切name都是channel;它發出的消息多是這樣的:
{ to: '/hello', from: '/requests/123-456-789', // 臨時建立的 method: 'GET' }
在Server這端,很明顯,/hello
是一個replication,並且不是一個sink
,若是你往這個Path發送一個message,沒有method屬性,它不知道怎麼處理;換句話說,/hello
是replication only的。
Client的from
,這個資源頗有意思,它首先是一個sink,這個id 123-456-789也應該是一次性的,客戶端即時分配它並且應該避免衝突,就像TCP的ephemeral port;
當前面的這個消息發送給Server時,它和異步的π邏輯是同樣的,就是Client發送給Server一個操做請求,同時給了它一個name/channel (from
),服務端能夠向這個name/channel發送結果,這樣就『模擬』了一個異步函數或者RPC過程。
這個from
路徑能夠是一次性的,在返回以後這個路徑就『消失』了,和前面說的reduction以後表達式消失了同樣;由於它象input prefix同樣是接受消息的,因此稱爲Sink;Sink的反義詞是Source,後面會遇到。
from
能夠同時是一個replication
。好比能夠接受GET
,返回一下請求的參數之類。可能功能上沒什麼意義,若是它僅僅是一個一次性的Sink,生命週期僅限於一個request/response消息來回的過程。
可是它的生命週期多是更長的,好比,服務端返回的並非一個JSON對象,而是一個Stream,一個Object Stream或者一個Binary Chunk Stream,或者混合,看協議怎麼設計。
這個時候這個from
路徑表示的資源就是一個Stream Sink了,便可以接收多個Message,那它須要表述的信息就豐富不少,好比能夠有進度、速度等等。這個時候它兼有replication的服務能力,接受Restful method操做就更有意義。
更復雜的狀況例子。
好比一個HTTP Post/Put/Patch,是須要upload一個stream的。
咱們會發現HTTP的設計不是原子化的,它的內部其實是先發出請求,等待了Server端應答100 Continue,而後繼續上傳內容的。
在RP裏,這些語義被扁平化了。Post/Put/Patch若是須要上傳流,Server端馬上返回一個即時建立的Sink Path。好比:
Client請求:
{ to: '/files', from: '/requests/123-456-789', method: 'POST', ... }
Server回答:
{ to: '/requests/123-456-789', status: 100, sink: '/files/#/sinks/3223a6f3' }
在Server的回答中,它即時分配了一個sink;以後客戶端能夠陸續向這個sink發送消息,構成一個stream。
顯式建立一個Sink標識,一方面能夠簡化routing,另外一方面,它更符合π的input prefix設計,即便用一個獨立的name/channel完成一個獨立計算任務;從流的意義上來講,這個input prefix可使用屢次,直到遇到EOF/Null-terminator後消失。
這裏有趣的地方是,在Client一端的/requests/123-456-789
,它是這個stream的Source標識。
它必須同時是一個replication,WHY?由於實際使用中必然會有取消流或者流控的需求,那服務端直接考慮操做這個資源就能夠了,好比:
{ to: `/requests/123-456-789`, method: 'PATCH', body: { flow: false } }
至關於暫停這個流。
若是這個流暫停了。在一個良好的實現下,在應用層應該相似把request實現成了stream.Writable
,這樣在應用層應該考慮出現drain
事件時纔會繼續向stream寫入。
這是什麼?這就是π裏的output prefix,對吧,消息的發出能夠block一個進程繼續執行,直到消息發出爲止。
雖然在π裏常見的是沒有buffer的狀況,output prefix和input prefix直接reduce成一個新的表達式;而在實際程序中,buffer老是要用到的,但buffer也必定是有限空間的,最終buffer滿了後output prefix會發生的。
異步編程和麪向通信的編程必定是這樣的所謂Lazy的,只是在不少語言裏實用producer-consumer模式實現,須要用很重的類和pattern;IMHO,在一個良好支持併發/異步的語言中,這個東西越輕越好,node.js裏的emitter, stream, callback都是良好設計的典範。
總結:
設計哲學上,單向的Message Passing是底層;Request/Response,Stream,Stream Control,都是上層;但不管哪一層,基於路徑的資源標識都是可用的,由於在π裏,一切皆name,一切name皆channel,這就是Restful裏的URI的真正含義,在RP裏發揮到極致。
Alan Kay在直覺上解釋了OO的本質是Message Passing,而Robin Milner在π裏給出了最小化的數學定義,我以爲本身不可能比兩個圖靈獎得到者加起來更聰明瞭,就躺倒接受先賢先聖的理念就能夠了,但願沒有誤讀。