PHP協程

協程

「協程」就是用戶態的線程

要理解是什麼是「用戶態的線程」,必然就要先理解什麼是「內核態的線程」。 內核態的線程是由操做系統來進行調度的,在切換線程上下文時,要先保存上一個線程的上下文,而後執行下一個線程,當條件知足時,切換回上一個線程,並恢復上下文。 協程也是如此,只不過,用戶態的線程不是由操做系統來調度的,而是由程序員來調度的,是在用戶態的 -- 摘自連接描述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次的請求完成

完!

相關文章
相關標籤/搜索