概念
咱們知道多程序和多執行緒是實現併發的有效方式。但多程序的上下文切換資源開銷太大;多執行緒開銷相比要小很多,也是現在主流的做法,但其的控制權在核心,從而使使用者(程式設計師)失去了對程式碼的控制,而且執行緒的上下文切換也是有一定開銷的。 這時為了解決以上問題,”協程”(coroutine)的概念就產生了。你可以將協程理解為更輕量級的執行緒。這種執行緒叫做“使用者空間執行緒“。協程,有下面兩個特點:
協同。因為是由程式設計師自己寫的排程策略,其通過協作而不是搶佔來進行切換
在使用者態完成建立,切換和銷燬
PHP對協程的支援是在迭代生成器的基礎上, 增加了可以回送資料給生成器的功能(呼叫者傳送資料給被呼叫的生成器函式)。 這就把生成器到呼叫者的單向通訊轉變為兩者之間的雙向通訊。
迭代器
迭代器的概念這裡就不贅述了。下面看看我們自己實現的一個迭代器。
class MyIterator implements Iterator
{
private $var = array();
public function __construct($array)
{
if (is_array($array)) {
$this->var = $array;
}
}
public function rewind() { // 第一次迭代時候會執行(或呼叫該方法的時候),後面的迭代將不會執行。
echo "rewinding\n";
reset($this->var);
}
public function current() {
$var = current($this->var);
echo "current: $var\n";
return $var;
}
public function key() {
$var = key($this->var);
echo "key: $var\n";
return $var;
}
public function next() { // 最後執行,就是執行完下面sleep(2)後再執行。(執行了next本次迭代才算結束)
$var = next($this->var);
echo "next: $var\n";
return $var;
}
public function valid() { // 當valid返回false的時候迭代結束
$var = $this->current() !== false;
echo "valid: {$var}\n";
return $var;
}
}
$values = array(1,2,3,4);
$it = new MyIterator($values);
foreach ($it as $a => $b) { // 進行迭代(每次迭代,會依次執行以下方法: rewind(特別之處見上面解釋), valid, current, key, next)
print "=====\n";
sleep(2);
}
輸出:
rewinding
current: 1 // 因為valid裡面呼叫了current, 這裡current出來一次
valid: 1
current: 1
key: 0
=====
next: 2
current: 2
valid: 1
current: 2
key: 1
=====
next: 3
current: 3
valid: 1
current: 3
key: 2
=====
next: 4
current: 4
valid: 1
current: 4
key: 3
=====
next:
current:
valid: // valid返回false,迭代結束
生成器
有了yeild的方法就是一個生成器(生成器實現了Iterator介面,即一個生成器有迭代器的特點)。生成器的實現如下:
function xrange($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i = $step) {
echo $i . "\n";
yield;
}
}
// foreach方式
foreach (xrange(1, 10) as $num) {
}
$gene = xrange(1, 10); // gene就是一個生成器物件
// current
$gene->current(); // 列印1
// next
$gene->next();
$gene->current() // 列印2
輸出:
1
2
3
4
5
6
7
8
9
10
1
2
生成器各方法詳解可看文件: http://php.net/manual/zh/class.generator.php
注意:
生成器不能像函式一樣直接呼叫,呼叫方法如下:
1. foreach他
2. send($value)
3. current / next…
yield
yield的語法很靈活,我們用下面的例子,讓大家能明白yield語法的使用。
用例1: 讓出cpu執行權
function task1 () {
for ($i = 1; $i <= 10; $i) {
echo "This is task 1 iteration $i.\n";
yield;// 遇到yield就會主動讓出CPU的執行權;
}
}
$a = task1();
$a->current(); // 執行第一次迭代
$a->send(1); // 喚醒當時讓出CPU執行權的yield
輸出:
This is task 1 iteration 1.
This is task 1 iteration 2.
用例2: yield的返回
// yield返回
function task2 () {
for ($i = 1; $i <= 10; $i) {
echo "This is task 2 iteration $i.\n";
yield "lm$i"; // 遇到yield就會主動讓出CPU的執行權,for暫停執行, 然後返回"lm"。放在yield後面的值就是返回值
}
}
$a = task2();
$res = $a->current(); // 第一次迭代, 遇到yield返回
var_dump($res);
$res = $a->send(1); // 喚醒yield, for繼續執行,遇到yield返回。
var_dump($res);
輸出:
This is task 2 iteration 1.
string(3) "lm1"
This is task 2 iteration 2.
string(3) "lm2"
用例3: yield接收值
function task3 () {
for ($i = 1; $i <= 10; $i) {
echo "This is task 3 iteration $i.\n";
$getValue = yield;// 遇到yield就會主動讓出CPU的執行權;send後,將send值賦值給getValue
echo $getValue . " ";
}
}
$a = task3();
$a->current();
$a->send("aa"); // 喚醒yield,並將"aa"值賦值給$getValue變數
輸出:
This is task 3 iteration 1.
aa This is task 3 iteration 2.
用例4: yeild接收和返回寫在一起
function task4 () {
for ($i = 1; $i <= 10; $i) {
echo "This is task 4 iteration $i.\n";
$ret = yield "lm$i"; // yield, 然後返回lm$i; 當send時,將send過來的值賦值給$ret;
echo $ret;
}
}
$a = task4();
var_dump($a->current()); // 返回lm1
var_dump($a->send("hhh ")); // 先喚醒yield, 將"hhh "賦值給$ret,再返回lm2
var_dump($a->send("www ")); // 先喚醒yield, 將"www "賦值給$ret,再返回lm3
輸出:
This is task 4 iteration 1.
string(3) "lm1"
hhh This is task 4 iteration 2.
string(3) "lm2"
www This is task 4 iteration 3.
string(3) "lm3"
結語:
如果你有看過鳥哥的這篇文章http://www.laruence.com/2015/05/28/3038.html,應該對協程有個深刻的認識。但裡面內容更適合中高階PHP工程師看,而且還得具備一定的作業系統的知識,所以我在此基礎上用更通俗的方式,闡明一下PHP的協程概念。協程很強大的功能但相對比較複雜, 也比較難被理解。個人目前還沒有遇到合適的場景來使用PHP協程,不過我猜測,由於可以在使用者層面實現多併發,所以多用於CLI模式下的web服務開發,比如Golang的goroutine並不是執行緒,而是協程。還有yield有雙向通訊的功能,所以還可以實現非同步服務,但需要自己寫排程器,比如鳥哥這篇部落格裡面的非阻塞IOweb伺服器就是靠協程實現非同步了實現的。
以上內容如果有錯誤還請留言交流。
写评论
很抱歉,必須登入網站才能發佈留言。