「協程」就是用戶態的線程
要理解是什麼是「用戶態的線程」,必然就要先理解什麼是「內核態的線程」。 內核態的線程是由操做系統來進行調度的,在切換線程上下文時,要先保存上一個線程的上下文,而後執行下一個線程,當條件知足時,切換回上一個線程,並恢復上下文。 協程也是如此,只不過,用戶態的線程不是由操做系統來調度的,而是由程序員來調度的,是在用戶態的 -- 摘自連接描述php
咱們有兩個函數 task1
,task2
,咱們來手動調度它們的執行順序,好比在task1
執行一半的時候去執行task2
,兩個或者多個函數之間交替執行(這就是協程
的概念)。程序員
咱們來個正常的函數調用方式:shell
<?php function task1() { echo "task1函數 執行1\n"; echo "task1函數 執行2\n"; } function task2() { echo "task2函數 執行1\n"; echo "task2函數 執行2\n"; } // 調度 task1(); task2();
可想而知,以上的輸出確定是:segmentfault
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();
以上輸出:oop
task1函數 執行1 task2函數 執行1
很好,以上結果達到了咱們的預期。可是怎麼讓函數裏的代碼往下執行呢?編碼
調用生成器的next
方法:操作系統
$task1->next(); $task2->next();
最後你將看到的輸出結果是兩個函數交替執行輸出的:.net
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()) { unset($tasks[$k]); } else { 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://segmentfault.com/'); }, 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次的請求完成
完!