天天看點

Kilim實作淺析(一)

    kilim是一個java的actor架構,讓你可以在jvm裡使用基于協程的actor模型,bluedavy曾經介紹過,這裡不再贅言。這篇blog的目的在于分析下kilim實作的基本原理,看看怎麼在jvm上實作協程。

    在一些語言層面上支援協程的語言,如lua、ruby,都是直接在vm級别支援協程,vm幫你做context的儲存和恢複。jvm沒有提供這樣的指令來儲存和恢複方法棧的狀态,是以kilim的實作還是需要在bytecode級别做文章。首先,試想下,如果是你來實作協程,你會怎麼做?協程的兩個基本原語resume和yield,resume運作協程,yield讓出執行權,下次resume的時候會從yield的地方重新執行,并且context保持不變。可見,你需要做這麼幾個事情:

1、在yield的時候儲存目前context。

2、在resume的時候恢複context,并根據pc計數來決定從哪裡恢複執行。

3、半協程的實作來說,還需要一個排程器來排程所有協程。

4、為了做到使用者代碼透明,可能需要某種手段去修改使用者代碼,自動幫你做上面三個事情。

    kilim的實作就是幹了這麼幾個事情:

1、利用位元組碼增強,将普通的java代碼轉換為支援協程的代碼。

2、在調用pausable方法的時候,如果pause了就儲存目前方法棧的state,停止執行目前協程,将控制權交給排程器

3、排程器負責排程就緒的協程

4、協程resume的時候,自動恢複state,根據協程的pc計數跳轉到上次執行的位置,繼續執行。

    下面是來自kilim文檔的一個例子,同一段代碼,在位元組碼增前前後的變化:

Kilim實作淺析(一)

左邊是原始代碼,右邊是通過位元組碼增強後的代碼。其中h方法是pausable的,也就是說可能被暫停阻塞的,g方法因為調用了h這個方法也變成了pausable。

首先看,原始的h方法是沒有傳入任何參數的,增強後的代碼,多了個參數fiber,指向目前的協程,同樣,g方法本來隻有一個參數n,現在在後面也多了個fiber類型的參數,同樣是指向目前執行的協程。

其次,在原始的g方法裡,一旦調用,馬上進入一個for循環。但是在增強後的代碼,多了個switch派發的過程,這就是前面提到的,根據目前的fiber的pc計數,跳轉到上一次執行的地方執行。如果是第一次resume,也就是啟動協程,那麼就将初始循環的i設定為0,進入原始代碼的循環部分。fiber有一個pc計數,稱為程式計數器,用于指向恢複context的時候需要跳轉到位置。

第三,在g方法裡調用h這個可被暫停阻塞的方法的時候,在h方法前後多了一些調用:

f.down();

h(f);

f.up();

kilim的fiber将每個pauseable方法的調用組織成一個棧,每個pauseable方法都有一個activation frame,翻譯過來可以稱為活動棧幀,這個棧幀記錄了目前的棧的state,注意這個棧跟java本身的方法調用棧區分開來,一個是vm層面的,一個是kilim架構層面的。這裡的down方法就是将棧向下延伸,表示将調用一個pauseable方法,并且設定目前state和pc計數。

調用了down之後,才是調用實際的h方法,最後還要調用一次up,顧名思義,就是說一次pauseable方法調用完成,fiber的活動棧要遞增一層,回到上一層。但是h方法調用可能出現四種情況:

1、正常的順利傳回,沒有狀态需要恢複,所謂not_pausing__no_state

2、也是正常傳回,有狀态需要恢複,也就是not_pausing__has_state

3、h方法暫停阻塞,目前沒有儲存狀态,需要儲存狀态,這是第一次暫停的時候,稱為pausing__no_state

4、h方法暫停阻塞,目前已經有狀态,不需要儲存狀态,這是第一次暫停之後的resume再次暫停,稱為pausing__has_state,通常不需要處理什麼。

第四,可以看到,在up之後,就要根據up傳回的上述4種狀态執行不同的邏輯:

if (f.ispausing){

    //第一次暫停,沒有狀态

   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)

   //正常傳回,有狀态需要恢複,恢複i和n

   state_i2 st = (state_i2) f.state;

   i = st.i1; n = st.i2;

}

這裡沒有處理not_pausing__no_state和pausing__has_state,因為這兩種情況在這裡不需要處理。

    通過上面的分析,我想大家對kilim的實作應該已經有一個很基本的認識。下一步,我們分析一個實際的代碼例子,檢視整個運作流程。

文章轉自莊周夢蝶  ,原文釋出時間2010-09-17