協程
“協程”就是使用者態的執行緒
要理解是什麼是”使用者態的執行緒”,必然就要先理解什麼是”核心態的執行緒”。 核心態的執行緒是由作業系統來進行排程的,在切換執行緒上下文時,要先儲存上一個執行緒的上下文,然後執行下一個執行緒,當條件滿足時,切換回上一個執行緒,並恢復上下文。 協程也是如此,只不過,使用者態的執行緒不是由作業系統來排程的,而是由程式設計師來排程的,是在使用者態的 – 摘自連線描述
關於”使用者態執行緒”,我們用個小例子來加深理解
我們有兩個函式 task1
,task2
,我們來手動排程它們的執行順序,比如在task1
執行一半的時候去執行task2
,兩個或者多個函式之間交替執行(這就是協程
的概念)。
我們來個正常的函式呼叫方式:
<?php
function task1()
{
echo "task1函式 執行1\n";
echo "task1函式 執行2\n";
}
function task2()
{
echo "task2函式 執行1\n";
echo "task2函式 執行2\n";
}
// 排程
task1();
task2();
可想而知,以上的輸出肯定是:
task1函式 執行第1
task1函式 執行第2
task2函式 執行第1
task2函式 執行第2
但是我想在程式輸出task1函式 執行1
之後就輸出task2函式 執行1
怎麼辦?
這個時候 yield 就派上用場了,PHP
裡的協程是需要藉助 yield 來完成的。記住,yield 不是協程,而是協程
需要藉助 yield 的特性來實現。
<?php
function task1()
{
echo "task1函式 執行1\n";
yield;
echo "task1函式 執行2\n";
}
function task2()
{
echo "task2函式 執行1\n";
yield;
echo "task2函式 執行2\n";
}
// 排程
$task1 = task1(); // 返回一個生成器
$task2 = task2(); // 返回一個生成器
$task1->current();
$task2->current();
以上輸出:
task1函式 執行1
task2函式 執行1
很好,以上結果達到了我們的預期。但是怎麼讓函式里的程式碼往下執行呢?
呼叫生成器的next
方法:
$task1->next();
$task2->next();
最後你將看到的輸出結果是兩個函式交替執行輸出的:
task1函式 執行1
task2函式 執行1
task1函式 執行2
task2函式 執行2
小段總結
以上的程式碼實現可以抽象出兩個概念,任務
和排程
,任務
就是task函式,排程
就是我們怎麼去呼叫這些task函式
排程器和任務生成器
上一個小段總結裡有兩個概念叫任務
和排程
,我們簡單的封裝個任務生成器和排程器
// 任務生成器
$createTask = (function () {
$tasks = [];
return function ($callback) use (&$tasks) {
$task = [
'task' => $callback(),
'id' => count($tasks) + 1,
];
array_push($tasks, $task);
return $task;
};
})();
// 排程器
function schedule($tasks)
{
$first = [];
while (!empty($tasks)) {
$task = array_shift($tasks);
if (!array_key_exists($task['id'], $first)) {
$first[$task['id']] = true;
$task['task']->current();
} else {
$task['task']->next();
}
if ($task['task']->valid()) {
array_push($tasks, $task);
}
}
}
使用
$tasks = [
$createTask(function () {
echo "任務1 執行第1次\n";
yield;
echo "任務1 執行第2次\n";
}),
$createTask(function () {
echo "任務2 執行第1次\n";
yield;
echo "任務2 執行第2次\n";
})
];
schedule($tasks);
輸出結果:
任務1 執行第1次
任務2 執行第1次
任務1 執行第2次
任務2 執行第2次
可以從結果看出,排程器已經實現了多個任務之間進行協作。
網路請求
現在有個需求!就是任務在遇到網路請求的時候,我們無需等待網路請求的響應結果,而是遇到網路請求的時候,把這個任務掛起,然後去執行其它任務,等網路請求收到響應結果了再通知我們處理
這時候需要我們用到非阻塞IO呼叫
相關技術,涉及到系統核心層面,想了解可以點選連結描述
在PHP裡我們需要安裝個擴充套件eio
,大家自行安裝
pecl install eio
編碼:
$tasks = [
$createTask(function () {
echo "任務1 執行第1次\n";
yield;
echo "任務1 執行第2次\n";
}),
$createTask(function () {
echo "任務2 執行第1次\n";
eio_custom(function () {
return file_get_contents('https://laravel-china.org');
}, EIO_PRI_DEFAULT, function ($data, $ret) {
echo "請求完成\n";
});
yield;
echo "任務2 執行第2次\n";
})
];
schedule($tasks);
eio_event_loop();
在任務2 執行第1次
的時候,遇到網路請求,我們把請求任務交給系統核心,然後切換到其它任務去,等請求任務完成後回撥我們傳入的函式。
輸出結果:
任務1 執行第1次
任務2 執行第1次
任務1 執行第2次
任務2 執行第2次
任務2 執行第1次的請求完成
完!
本作品採用《CC 協議》,轉載必須註明作者和本文連結