by warezhou 2014.11.24
上次通過c擴充套件為php新增coroutine嘗試失敗之後,由於短期內啃下zend可能性幾乎為零,只能打語言原生能力的主意了。google之後發現,php5.5引入了generator和coroutine新特性,於是才有了本文的誕生。
《當c/c++後台開發遇上coroutine》
《一次失敗的php擴充套件開發之旅》
function my_range($start, $end, $step = 1)
}foreach (my_range(1, 1000) as $num)
/* * 1
* 2* ...
* 1000
*/
圖 1 基於generator的range()實現
$range = my_range(1, 1000);
var_dump($range);
/* * object(generator)#1 (0)
*/var_dump($range instanceof iterator);
/* * bool(true)
*/
圖 2 my_range()的實現推測
由於接觸php時日尚淺,並未深入語言實現細節,所以只能根據現象進行猜測,以下是我的一些個人理解:
細心的讀者可能已經發現,截至目前,其實generator已經實現了coroutine的關鍵特性:中斷執行、恢復執行。按照《當c/c++後台開發遇上coroutine》的思路,借助「全域性變數」一類語言設施進行資訊傳遞,實現非同步server應該足夠了。
其實相對於swapcontext族函式,generator已經前進了一大步,具備了「返回資料」的能力,如果同時具備「傳送資料」的能力,就再也不必通過那些蹩腳的手法繞路而行了。在php裡面,通過generator的send()介面(注意:不再是next()介面),可以完成「傳送資料」的任務,從而實現了真正的「雙向通訊」。
function gen()
$gen = gen();
$ret = $gen->current();
echo "[main]", $ret, "\n";
$ret = $gen->send("send1");
echo "[main]", $ret, "\n";
$ret = $gen->send("send2");
echo "[main]", $ret, "\n";
/* * [main]yield1
* [gen]send1
* [main]yield2
* [gen]send2
* [main]
*/
圖 3 coroutine雙向通訊示例
作為c/c++系碼農,發現「可重入」、「雙向通訊」能力之後,貌似沒有更多奢求了,不過php還是比較慷慨,繼續新增了exception機制,「錯誤處理」機制得到進一步完善。
function gen() catch (exception $ex)
echo "[gen]finish\n";
}$gen = gen();
$ret = $gen->current();
echo "[main]", $ret, "\n";
$ret = $gen->send("send1");
echo "[main]", $ret, "\n";
$ret = $gen->throw(new exception("test"));
echo "[main]", $ret, "\n";
/* * [main]yield1
* [gen]send1
* [main]yield2
* [gen][exception]test
* [gen]finish
* [main]
*/
圖 4 coroutine錯誤處理示例
前面簡單介紹了相關的語言設施,那麼具體到實際專案中,到底應該如何運用呢?讓我們繼續《一次失敗的php擴充套件開發之旅》描述的場景,借助上述特性實現那個美好的願望:以同步方式書寫非同步**
!
<?php
class asyncserver
if (!socket_set_nonblock($this->socket))
if(!socket_bind($this->socket, "0.0.0.0", 1234))
}public function run()
$writes = null;
$excepts= null;
if (!socket_select($reads, $writes, $excepts, 0, 1000))
foreach ($reads as $one)
if ($one == $this->socket)
if (!socket_set_nonblock($socket))
socket_sendto($socket, $task->data, $task->len, 0, $task->ip, $task->port);
$this->tasks[$socket] = [$socket, $coroutine];
} else else }}
}}}class asynctask
}function requesthandler($socket, $req_buf, $req_len, $ip, $port)
$server = new asyncserver(requesthandler);
$server->run();
?>
**解讀:
第一版遺留問題:
<?php
class asyncserver
if (!socket_set_nonblock($this->socket))
if(!socket_bind($this->socket, "0.0.0.0", 1234))
}public function run()
unset($this->timers[$time]);
}$reads = array($this->socket);
foreach ($this->tasks as list($socket))
$writes = null;
$excepts= null;
if (!socket_select($reads, $writes, $excepts, 0, 1000))
foreach ($reads as $one)
if ($one == $this->socket)
$task = $coroutine->current();
//echo "[run]asynctask recv. data=$task->data ip=$task->ip port=$task->port timeout=$task->timeout\n";
$socket = socket_create(af_inet, sock_dgram, sol_udp);
if(!$socket)
if (!socket_set_nonblock($socket))
socket_sendto($socket, $task->data, $task->len, 0, $task->ip, $task->port);
$deadline = $now + $task->timeout;
$this->tasks[$socket] = [$socket, $coroutine, $deadline];
$this->timers[$deadline][$socket] = $socket;
} else }}
}}class asynctask
}function asyncsendrecv($req_buf, $req_len, $ip, $port, $timeout)
function requesthandler($socket, $req_buf, $req_len, $ip, $port) catch (exception $ex)
//echo "[requesthandler] after yield asynctask. rsp=$rsp_buf\n";
socket_sendto($socket, $rsp_buf, $rsp_len, 0, $ip, $port);
}$server = new asyncserver(requesthandler);
$server->run();
?>
**解讀:
攜程小程式初體驗
隨著小程式的大熱,作為乙個程式猿,我也開始接觸並且大概了解了乙個製作小程式的一些過程,為了提高自己的動手能力,於是乎,我開始來仿寫攜程的小程式,來實現一些基本功能,在仿寫的過程中,也遇到了一些難題,也有了一點收穫,希望可以通過這篇文章與大家共同交流,共同進步。為了更好的開發,我們需要準備我們需要的工...
php 協程理解
生成器生成器最基本的思想也是乙個函式,這個函式的返回值是依次輸出,而不是只返回乙個單獨的值。或者,換句話說,生成器使你更方便的實現了迭代器介面。下面通過實現乙個xrange函式來簡單說明 function xrange start,end,step 1 foreach xrange 1,100000...
協程巢狀協程
import asyncio import functools 第三層協程 async def test1 print 我是test1 await asyncio.sleep 1 print test1已經睡了1秒 await asyncio.sleep 3 print test1又睡了3秒 ret...