天天看點

以swoole為例,學習如何實作協程聊聊Swoole2.0協程

swoole 2.0正式版釋出了。2.0版本最大的更新是增加了對協程(coroutine)的支援。正式版已同時支援php5和php7。基于swoole2.0協程php開發者可以已同步的方式編寫代碼,底層自動進行協程排程,轉變為異步io。解決了傳統異步程式設計嵌套回調的問題。

目前swoole底層内置的協程用戶端元件包括:udpclient、tcpclient、httpclient、redisclient、mysqlclient,基本涵蓋了開發者常用的幾種通信協定。協程元件隻能在伺服器的onconnect、onrequest、onreceive、onmessage 回調函數中使用。

注意,swoole 2.0.5以前的版本還是灰階測試版本,可能會存在問題。 beta是因為協程是全新的版本。


咱們就以上面的示例代碼為例,說一說協程的執行流程。

http server監聽9051端口。當有相關事件發生時,如有資料到達,就會執行綁定到request上的回調函數。在執行回調函數之前,會建立一個協程。這時,會儲存cpu寄存器的狀态和zendvm stack資訊。

在回調函數執行過程中,如果遇到io操作,如<code>$tcp_cli-&gt;connect(</code>,就會儲存目前的狀态,并讓出cpu使用權。目前請求執行被挂起。

讓cpu出使用權後,cpu就可以用于處理其他事件。如處理其他用戶端的request請求。

當被挂起的請求,又有新的事件發生,如上面<code>$tcp_cli-&gt;connect()</code>的資料已經傳回。這時,會使用挂起前儲存的狀态資訊恢複,然後繼續執行回調函數。

如果在執行過程中,再次遇到io操作,會繼續執行儲存狀态和讓出cpu使用權。

這些io操作都是非阻塞的,即發送請求和擷取資料分為兩步。當請求發送完畢後,就會進行狀态儲存和讓出cpu使用權。在等待請求資料返的這段時間,cpu可以執行一些其他程式。這樣就可以充分利用cpu。

swoole的協程是基于 setjmp 、 longjmp 實作的。swoole為每個協程都配置設定了空間,用于儲存協程切換時的狀态資訊。進行協程切換時會自動儲存zend vm的記憶體狀态(主要是eg全局記憶體和vm stack)。當回調函數執行完畢後,會自動銷毀配置設定的空間。

什麼時候會建立協程?在server的onconnect、onrequest、onreceive、onmessage 回調函數被執行前會建立一個協程。

協程建立的方法是<code>coro_create</code>。相關源碼可以檢視<code>swoole_coroutine.c</code>檔案。

<code>coro_create</code>方法中主要進行了如下操作:

什麼時候會讓出cpu執行權?當回調函數中遇到異步io的時候,會讓出cpu執行權。如,代碼中的connect操作。下面,我們就以connect操作為例,看看讓出cpu執行權時都做了那些操作。

connect的相關代碼在<code>swoole_coroutine.c</code>檔案中。代碼如下:

所謂的協程資訊主要就是目前的上下文執行資訊。<code>coro_save</code>方法在<code>swoole_coroutine.c</code>檔案中。代碼如下:

<code>coro_yield</code>方法的作用是讓出cpu執行權。代碼在<code>swoole_coroutine.c</code>檔案中。

在這個方法中主要進行了還原棧資訊和longjump操作。

corog.origin_vm_stack 這些棧資訊的初始化在<code>coro_init</code>方法中。記錄了協程執行前的狀态。

當異步io有資料傳回後,會進行協程恢複。協程恢複的方法是<code>coro_resume</code>。在<code>swoole_coroutine.c</code>檔案中。代碼如下:

可見,建立協程和恢複協程的整體代碼結構差不多。

當回到函數執行完畢後,會結束協程。

<code>coro_close</code>方法用于結束協程。源碼在<code>swoole_coroutine.c</code>檔案中。