Kilim是一個Java的actor框架,讓你可以在JVM里使用基于協(xié)程的actor模型,bluedavy曾經(jīng)介紹過,這里不再贅言。這篇blog的目的在于分析下kilim實現(xiàn)的基本原理,看看怎么在JVM上實現(xiàn)協(xié)程。
在一些語言層面上支持協(xié)程的語言,如lua、ruby,都是直接在VM級別支持協(xié)程,VM幫你做context的保存和恢復。JVM沒有提供這樣的指令來保存和恢復方法棧的狀態(tài),因此kilim的實現(xiàn)還是需要在bytecode級別做文章。首先,試想下,如果是你來實現(xiàn)協(xié)程,你會怎么做?協(xié)程的兩個基本原語resume和yield,resume運行協(xié)程,yield讓出執(zhí)行權(quán),下次resume的時候會從yield的地方重新執(zhí)行,并且context保持不變??梢?,你需要做這么幾個事情:
1、在yield的時候保存當前context。
2、在resume的時候恢復context,并根據(jù)pc計數(shù)來決定從哪里恢復執(zhí)行。
3、半?yún)f(xié)程的實現(xiàn)來說,還需要一個調(diào)度器來調(diào)度所有協(xié)程。
4、為了做到用戶代碼透明,可能需要某種手段去修改用戶代碼,自動幫你做上面三個事情。
kilim的實現(xiàn)就是干了這么幾個事情:
1、利用字節(jié)碼增強,將普通的java代碼轉(zhuǎn)換為支持協(xié)程的代碼。
2、在調(diào)用pausable方法的時候,如果pause了就保存當前方法棧的State,停止執(zhí)行當前協(xié)程,將控制權(quán)交給調(diào)度器
3、調(diào)度器負責調(diào)度就緒的協(xié)程
4、協(xié)程resume的時候,自動恢復State,根據(jù)協(xié)程的pc計數(shù)跳轉(zhuǎn)到上次執(zhí)行的位置,繼續(xù)執(zhí)行。
下面是來自kilim文檔的一個例子,同一段代碼,在字節(jié)碼增前前后的變化:
左邊是原始代碼,右邊是通過字節(jié)碼增強后的代碼。其中h方法是pausable的,也就是說可能被暫停阻塞的,g方法因為調(diào)用了h這個方法也變成了pausable。
首先看,原始的h方法是沒有傳入任何參數(shù)的,增強后的代碼,多了個參數(shù)Fiber,指向當前的協(xié)程,同樣,g方法本來只有一個參數(shù)n,現(xiàn)在在后面也多了個Fiber類型的參數(shù),同樣是指向當前執(zhí)行的協(xié)程。
其次,在原始的g方法里,一旦調(diào)用,馬上進入一個for循環(huán)。但是在增強后的代碼,多了個switch派發(fā)的過程,這就是前面提到的,根據(jù)當前的Fiber的pc計數(shù),跳轉(zhuǎn)到上一次執(zhí)行的地方執(zhí)行。如果是第一次resume,也就是啟動協(xié)程,那么就將初始循環(huán)的i設(shè)置為0,進入原始代碼的循環(huán)部分。Fiber有一個pc計數(shù),稱為程序計數(shù)器,用于指向恢復context的時候需要跳轉(zhuǎn)到位置。
第三,在g方法里調(diào)用h這個可被暫停阻塞的方法的時候,在h方法前后多了一些調(diào)用:
f.down();
h(f);
f.up();
kilim的Fiber將每個pauseable方法的調(diào)用組織成一個棧,每個pauseable方法都有一個activation frame,翻譯過來可以稱為活動棧幀,這個棧幀記錄了當前的棧的State,注意這個棧跟java本身的方法調(diào)用棧區(qū)分開來,一個是VM層面的,一個是kilim框架層面的。這里的down方法就是將棧向下延伸,表示將調(diào)用一個pauseable方法,并且設(shè)置當前State和pc計數(shù)。
調(diào)用了down之后,才是調(diào)用實際的h方法,最后還要調(diào)用一次up,顧名思義,就是說一次pauseable方法調(diào)用完成,fiber的活動棧要遞增一層,回到上一層。但是h方法調(diào)用可能出現(xiàn)四種情況:
1、正常的順利返回,沒有狀態(tài)需要恢復,所謂NOT_PAUSING__NO_STATE
2、也是正常返回,有狀態(tài)需要恢復,也就是NOT_PAUSING__HAS_STATE
3、h方法暫停阻塞,當前沒有保存狀態(tài),需要保存狀態(tài),這是第一次暫停的時候,稱為PAUSING__NO_STATE
4、h方法暫停阻塞,當前已經(jīng)有狀態(tài),不需要保存狀態(tài),這是第一次暫停之后的resume再次暫停,稱為PAUSING__HAS_STATE,通常不需要處理什么。
第四,可以看到,在up之后,就要根據(jù)up返回的上述4種狀態(tài)執(zhí)行不同的邏輯:
if (f.isPausing){
//第一次暫停,沒有狀態(tài)
if (!f.hasState){
//new一個State_I2,并保存i和n
f.state = new State_I2(i,n);
//記錄pc,還記的前面的switch嗎?
f.pc = H1;
}
return;
} else if (f.hasState)
//正常返回,有狀態(tài)需要恢復,恢復i和n
State_I2 st = (State_I2) f.state;
i = st.i1; n = st.i2;
}
這里沒有處理NOT_PAUSING__NO_STATE和PAUSING__HAS_STATE,因為這兩種情況在這里不需要處理。
通過上面的分析,我想大家對kilim的實現(xiàn)應該已經(jīng)有一個很基本的認識。下一步,我們分析一個實際的代碼例子,查看整個運作流程。