成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

PHP7下的協(xié)程實(shí)現(xiàn)

young.li / 1382人閱讀

摘要:把中的初始化方法改下,因?yàn)槲覀冊谶\(yùn)行一個(gè)的時(shí)候,我們要分析出他包含了哪些子協(xié)程,然后將子協(xié)程用一個(gè)堆棧保存??偨Y(jié)這下應(yīng)該明白怎么實(shí)現(xiàn)協(xié)程了吧建議不要使用的來實(shí)現(xiàn)協(xié)程,推薦使用,已經(jīng)支持了協(xié)程,并附帶了部分案例。

前言

相信大家都聽說過『協(xié)程』這個(gè)概念吧。

但是有些同學(xué)對這個(gè)概念似懂非懂,不知道怎么實(shí)現(xiàn),怎么用,用在哪,甚至有些人認(rèn)為yield就是協(xié)程!

我始終相信,如果你無法準(zhǔn)確地表達(dá)出一個(gè)知識點(diǎn)的話,我可以認(rèn)為你就是不懂。

如果你之前了解過利用PHP實(shí)現(xiàn)協(xié)程的話,你肯定看過鳥哥的那篇文章:在PHP中使用協(xié)程實(shí)現(xiàn)多任務(wù)調(diào)度| 風(fēng)雪之隅

鳥哥這篇文章是從國外的作者翻譯來的,翻譯的簡潔明了,也給出了具體的例子了。

我寫這篇文章的目的,是想對鳥哥文章做更加充足的補(bǔ)充,畢竟有部分同學(xué)的基礎(chǔ)還是不夠好,看得也是云頭霧里的。

歡迎微博關(guān)注我 @碼云 。2. 全互聯(lián)網(wǎng)最全的PHP技能圖譜:https://bruceit.com/skills

什么是協(xié)程

先搞清楚,什么是協(xié)程。

你可能已經(jīng)聽過『進(jìn)程』和『線程』這兩個(gè)概念。

進(jìn)程就是二進(jìn)制可執(zhí)行文件在計(jì)算機(jī)內(nèi)存里的一個(gè)運(yùn)行實(shí)例,就好比你的.exe文件是個(gè)類,進(jìn)程就是new出來的那個(gè)實(shí)例。

進(jìn)程是計(jì)算機(jī)系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位(調(diào)度單位這里別糾結(jié)線程進(jìn)程的),每個(gè)CPU下同一時(shí)刻只能處理一個(gè)進(jìn)程。

所謂的并行,只不過是看起來并行,CPU事實(shí)上在用很快的速度切換不同的進(jìn)程。

進(jìn)程的切換需要進(jìn)行系統(tǒng)調(diào)用,CPU要保存當(dāng)前進(jìn)程的各個(gè)信息,同時(shí)還會(huì)使CPUCache被廢掉。

所以進(jìn)程切換不到非不得已就不做。

那么怎么實(shí)現(xiàn)『進(jìn)程切換不到非不得已就不做』呢?

首先進(jìn)程被切換的條件是:進(jìn)程執(zhí)行完畢、分配給進(jìn)程的CPU時(shí)間片結(jié)束,系統(tǒng)發(fā)生中斷需要處理,或者進(jìn)程等待必要的資源(進(jìn)程阻塞)等。你想下,前面幾種情況自然沒有什么話可說,但是如果是在阻塞等待,是不是就浪費(fèi)了。

其實(shí)阻塞的話我們的程序還有其他可執(zhí)行的地方可以執(zhí)行,不一定要傻傻的等!

所以就有了線程。

線程簡單理解就是一個(gè)『微進(jìn)程』,專門跑一個(gè)函數(shù)(邏輯流)。

所以我們就可以在編寫程序的過程中將可以同時(shí)運(yùn)行的函數(shù)用線程來體現(xiàn)了。

線程有兩種類型,一種是由內(nèi)核來管理和調(diào)度。

我們說,只要涉及需要內(nèi)核參與管理調(diào)度的,代價(jià)都是很大的。這種線程其實(shí)也就解決了當(dāng)一個(gè)進(jìn)程中,某個(gè)正在執(zhí)行的線程遇到阻塞,我們可以調(diào)度另外一個(gè)可運(yùn)行的線程來跑,但是還是在同一個(gè)進(jìn)程里,所以沒有了進(jìn)程切換。

還有另外一種線程,他的調(diào)度是由程序員自己寫程序來管理的,對內(nèi)核來說不可見。這種線程叫做『用戶空間線程』。

協(xié)程可以理解就是一種用戶空間線程。

協(xié)程,有幾個(gè)特點(diǎn):

協(xié)同,因?yàn)槭怯沙绦騿T自己寫的調(diào)度策略,其通過協(xié)作而不是搶占來進(jìn)行切換

在用戶態(tài)完成創(chuàng)建,切換和銷毀

?? 從編程角度上看,協(xié)程的思想本質(zhì)上就是控制流的主動(dòng)讓出(yield)和恢復(fù)(resume)機(jī)制

generator經(jīng)常用來實(shí)現(xiàn)協(xié)程

說到這里,你應(yīng)該明白協(xié)程的基本概念了吧?

PHP實(shí)現(xiàn)協(xié)程

一步一步來,從解釋概念說起!

可迭代對象

PHP5提供了一種定義對象的方法使其可以通過單元列表來遍歷,例如用foreach語句。

你如果要實(shí)現(xiàn)一個(gè)可迭代對象,你就要實(shí)現(xiàn)Iterator接口:

var = $array;
        }
    }

    public function rewind() {
        echo "rewinding
";
        reset($this->var);
    }

    public function current() {
        $var = current($this->var);
        echo "current: $var
";
        return $var;
    }

    public function key() {
        $var = key($this->var);
        echo "key: $var
";
        return $var;
    }

    public function next() {
        $var = next($this->var);
        echo "next: $var
";
        return $var;
    }

    public function valid() {
        $var = $this->current() !== false;
        echo "valid: {$var}
";
        return $var;
    }
}

$values = array(1,2,3);
$it = new MyIterator($values);

foreach ($it as $a => $b) {
    print "$a: $b
";
}
生成器

可以說之前為了擁有一個(gè)能夠被foreach遍歷的對象,你不得不去實(shí)現(xiàn)一堆的方法,yield關(guān)鍵字就是為了簡化這個(gè)過程。

生成器提供了一種更容易的方法來實(shí)現(xiàn)簡單的對象迭代,相比較定義類實(shí)現(xiàn)Iterator接口的方式,性能開銷和復(fù)雜性大大降低。


記住,一個(gè)函數(shù)中如果用了yield,他就是一個(gè)生成器,直接調(diào)用他是沒有用的,不能等同于一個(gè)函數(shù)那樣去執(zhí)行!

所以,yield就是yield,下次誰再說yield是協(xié)程,我肯定把你xxxx。

PHP協(xié)程

前面介紹協(xié)程的時(shí)候說了,協(xié)程需要程序員自己去編寫調(diào)度機(jī)制,下面我們來看這個(gè)機(jī)制怎么寫。

0)生成器正確使用

既然生成器不能像函數(shù)一樣直接調(diào)用,那么怎么才能調(diào)用呢?

方法如下:

foreach他

send($value)

current / next...

1)Task實(shí)現(xiàn)

Task就是一個(gè)任務(wù)的抽象,剛剛我們說了協(xié)程就是用戶空間線程,線程可以理解就是跑一個(gè)函數(shù)。

所以Task的構(gòu)造函數(shù)中就是接收一個(gè)閉包函數(shù),我們命名為coroutine。

/**
 * Task任務(wù)類
 */
class Task
{
    protected $taskId;
    protected $coroutine;
    protected $beforeFirstYield = true;
    protected $sendValue;

    /**
     * Task constructor.
     * @param $taskId
     * @param Generator $coroutine
     */
    public function __construct($taskId, Generator $coroutine)
    {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
    }

    /**
     * 獲取當(dāng)前的Task的ID
     * 
     * @return mixed
     */
    public function getTaskId()
    {
        return $this->taskId;
    }

    /**
     * 判斷Task執(zhí)行完畢了沒有
     * 
     * @return bool
     */
    public function isFinished()
    {
        return !$this->coroutine->valid();
    }

    /**
     * 設(shè)置下次要傳給協(xié)程的值,比如 $id = (yield $xxxx),這個(gè)值就給了$id了
     * 
     * @param $value
     */
    public function setSendValue($value)
    {
        $this->sendValue = $value;
    }

    /**
     * 運(yùn)行任務(wù)
     * 
     * @return mixed
     */
    public function run()
    {
        // 這里要注意,生成器的開始會(huì)reset,所以第一個(gè)值要用current獲取
        if ($this->beforeFirstYield) {
            $this->beforeFirstYield = false;
            return $this->coroutine->current();
        } else {
            // 我們說過了,用send去調(diào)用一個(gè)生成器
            $retval = $this->coroutine->send($this->sendValue);
            $this->sendValue = null;
            return $retval;
        }
    }
}
2)Scheduler實(shí)現(xiàn)

接下來就是Scheduler這個(gè)重點(diǎn)核心部分,他扮演著調(diào)度員的角色。

/**
 * Class Scheduler
 */
Class Scheduler
{
    /**
     * @var SplQueue
     */
    protected $taskQueue;
    /**
     * @var int
     */
    protected $tid = 0;

    /**
     * Scheduler constructor.
     */
    public function __construct()
    {
        /* 原理就是維護(hù)了一個(gè)隊(duì)列,
         * 前面說過,從編程角度上看,協(xié)程的思想本質(zhì)上就是控制流的主動(dòng)讓出(yield)和恢復(fù)(resume)機(jī)制
         * */
        $this->taskQueue = new SplQueue();
    }

    /**
     * 增加一個(gè)任務(wù)
     *
     * @param Generator $task
     * @return int
     */
    public function addTask(Generator $task)
    {
        $tid = $this->tid;
        $task = new Task($tid, $task);
        $this->taskQueue->enqueue($task);
        $this->tid++;
        return $tid;
    }

    /**
     * 把任務(wù)進(jìn)入隊(duì)列
     *
     * @param Task $task
     */
    public function schedule(Task $task)
    {
        $this->taskQueue->enqueue($task);
    }

    /**
     * 運(yùn)行調(diào)度器
     */
    public function run()
    {
        while (!$this->taskQueue->isEmpty()) {
            // 任務(wù)出隊(duì)
            $task = $this->taskQueue->dequeue();
            $res = $task->run(); // 運(yùn)行任務(wù)直到 yield

            if (!$task->isFinished()) {
                $this->schedule($task); // 任務(wù)如果還沒完全執(zhí)行完畢,入隊(duì)等下次執(zhí)行
            }
        }
    }
}

這樣我們基本就實(shí)現(xiàn)了一個(gè)協(xié)程調(diào)度器。

你可以使用下面的代碼來測試:

addTask(task1()); // 添加不同的閉包函數(shù)作為任務(wù)
$scheduler->addTask(task2());
$scheduler->run();

關(guān)鍵說下在哪里能用得到PHP協(xié)程。

function task1() {
        /* 這里有一個(gè)遠(yuǎn)程任務(wù),需要耗時(shí)10s,可能是一個(gè)遠(yuǎn)程機(jī)器抓取分析遠(yuǎn)程網(wǎng)址的任務(wù),我們只要提交最后去遠(yuǎn)程機(jī)器拿結(jié)果就行了 */
        remote_task_commit();
        // 這時(shí)候請求發(fā)出后,我們不要在這里等,主動(dòng)讓出CPU的執(zhí)行權(quán)給task2運(yùn)行,他不依賴這個(gè)結(jié)果
        yield;
        yield (remote_task_receive());
        ...
}
 
function task2() {
    for ($i = 1; $i <= 5; ++$i) {
        echo "This is task 2 iteration $i.
";
        yield; // 主動(dòng)讓出CPU的執(zhí)行權(quán)
    }
}

這樣就提高了程序的執(zhí)行效率。

關(guān)于『系統(tǒng)調(diào)用』的實(shí)現(xiàn),鳥哥已經(jīng)講得很明白,我這里不再說明。

3)協(xié)程堆棧

鳥哥文中還有一個(gè)協(xié)程堆棧的例子。

我們上面說過了,如果在函數(shù)中使用了yield,就不能當(dāng)做函數(shù)使用。

所以你在一個(gè)協(xié)程函數(shù)中嵌套另外一個(gè)協(xié)程函數(shù):

addTask(task());
$scheduler->run();

這里的echoTimes是執(zhí)行不了的!所以就需要協(xié)程堆棧。

不過沒關(guān)系,我們改一改我們剛剛的代碼。

把Task中的初始化方法改下,因?yàn)槲覀冊谶\(yùn)行一個(gè)Task的時(shí)候,我們要分析出他包含了哪些子協(xié)程,然后將子協(xié)程用一個(gè)堆棧保存。(C語言學(xué)的好的同學(xué)自然能理解這里,不理解的同學(xué)我建議去了解下進(jìn)程的內(nèi)存模型是怎么處理函數(shù)調(diào)用)

 /**
     * Task constructor.
     * @param $taskId
     * @param Generator $coroutine
     */
    public function __construct($taskId, Generator $coroutine)
    {
        $this->taskId = $taskId;
        // $this->coroutine = $coroutine;
        // 換成這個(gè),實(shí)際Task->run的就是stackedCoroutine這個(gè)函數(shù),不是$coroutine保存的閉包函數(shù)了
        $this->coroutine = stackedCoroutine($coroutine); 
    }

當(dāng)Task->run()的時(shí)候,一個(gè)循環(huán)來分析:

/**
 * @param Generator $gen
 */
function stackedCoroutine(Generator $gen)
{
    $stack = new SplStack;

    // 不斷遍歷這個(gè)傳進(jìn)來的生成器
    for (; ;) {
        // $gen可以理解為指向當(dāng)前運(yùn)行的協(xié)程閉包函數(shù)(生成器)
        $value = $gen->current(); // 獲取中斷點(diǎn),也就是yield出來的值

        if ($value instanceof Generator) {
            // 如果是也是一個(gè)生成器,這就是子協(xié)程了,把當(dāng)前運(yùn)行的協(xié)程入棧保存
            $stack->push($gen);
            $gen = $value; // 把子協(xié)程函數(shù)給gen,繼續(xù)執(zhí)行,注意接下來就是執(zhí)行子協(xié)程的流程了
            continue;
        }

        // 我們對子協(xié)程返回的結(jié)果做了封裝,下面講
        $isReturnValue = $value instanceof CoroutineReturnValue; // 子協(xié)程返回`$value`需要主協(xié)程幫忙處理
        
        if (!$gen->valid() || $isReturnValue) {
            if ($stack->isEmpty()) {
                return;
            }
            // 如果是gen已經(jīng)執(zhí)行完畢,或者遇到子協(xié)程需要返回值給主協(xié)程去處理
            $gen = $stack->pop(); //出棧,得到之前入棧保存的主協(xié)程
            $gen->send($isReturnValue ? $value->getValue() : NULL); // 調(diào)用主協(xié)程處理子協(xié)程的輸出值
            continue;
        }

        $gen->send(yield $gen->key() => $value); // 繼續(xù)執(zhí)行子協(xié)程
    }
}

然后我們增加echoTime的結(jié)束標(biāo)示:

class CoroutineReturnValue {
    protected $value;
 
    public function __construct($value) {
        $this->value = $value;
    }
     
    // 獲取能把子協(xié)程的輸出值給主協(xié)程,作為主協(xié)程的send參數(shù)
    public function getValue() {
        return $this->value;
    }
}

function retval($value) {
    return new CoroutineReturnValue($value);
}

然后修改echoTimes

function echoTimes($msg, $max) {
    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i
";
        yield;
    }
    yield retval("");  // 增加這個(gè)作為結(jié)束標(biāo)示
}

Task變?yōu)椋?/p>

function task1()
{
    yield echoTimes("bar", 5);
}

這樣就實(shí)現(xiàn)了一個(gè)協(xié)程堆棧,現(xiàn)在你可以舉一反三了。

4)PHP7中yield from關(guān)鍵字

PHP7中增加了yield from,所以我們不需要自己實(shí)現(xiàn)攜程堆棧,真是太好了。

把Task的構(gòu)造函數(shù)改回去:

    public function __construct($taskId, Generator $coroutine)
    {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
        // $this->coroutine = stackedCoroutine($coroutine); //不需要自己實(shí)現(xiàn)了,改回之前的
    }

echoTimes函數(shù):

function echoTimes($msg, $max) {
    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i
";
        yield;
    }
}

task1生成器:

function task1()
{
    yield from echoTimes("bar", 5);
}

這樣,輕松調(diào)用子協(xié)程。

總結(jié)

這下應(yīng)該明白怎么實(shí)現(xiàn)PHP協(xié)程了吧?

建議不要使用PHP的Yield來實(shí)現(xiàn)協(xié)程,推薦使用swoole,2.0已經(jīng)支持了協(xié)程,并附帶了部分案例。

End...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/26223.html

相關(guān)文章

  • PHP回顧之協(xié)程

    摘要:本文先回顧生成器,然后過渡到協(xié)程編程。其作用主要體現(xiàn)在三個(gè)方面數(shù)據(jù)生成生產(chǎn)者,通過返回?cái)?shù)據(jù)數(shù)據(jù)消費(fèi)消費(fèi)者,消費(fèi)傳來的數(shù)據(jù)實(shí)現(xiàn)協(xié)程。解決回調(diào)地獄的方式主要有兩種和協(xié)程。重點(diǎn)應(yīng)當(dāng)關(guān)注控制權(quán)轉(zhuǎn)讓的時(shí)機(jī),以及協(xié)程的運(yùn)作方式。 轉(zhuǎn)載請注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請求 cookie web響應(yīng) sess...

    Java3y 評論0 收藏0
  • Swoole協(xié)程之旅-前篇

    摘要:協(xié)程完全有用戶態(tài)程序控制,所以也被成為用戶態(tài)的線程。目前支持協(xié)程的語言有很多,例如等。協(xié)程之旅前篇結(jié)束,下一篇文章我們將深入分析原生協(xié)程部分的實(shí)現(xiàn)。 寫在最前 ??Swoole協(xié)程經(jīng)歷了幾個(gè)里程碑,我們需要在前進(jìn)的道路上不斷總結(jié)與回顧自己的發(fā)展歷程,正所謂溫故而知新,本系列文章將分為協(xié)程之旅前、中、后三篇。 前篇主要介紹協(xié)程的概念和Swoole幾個(gè)版本協(xié)程實(shí)現(xiàn)的主要方案技術(shù); 中篇主...

    terasum 評論0 收藏0
  • Swoole 4.4 協(xié)程搶占式調(diào)度器詳解

    摘要:搶占式調(diào)度我們在今年年初就計(jì)劃實(shí)現(xiàn)的搶占式調(diào)度,以滿足實(shí)現(xiàn)有些場景下的不均衡調(diào)度帶來的問題。考慮開線程,負(fù)責(zé)檢查當(dāng)前執(zhí)行協(xié)程執(zhí)行時(shí)間。達(dá)到我們的第二個(gè)協(xié)程主動(dòng)搶占第一個(gè)協(xié)程的效果。 前言 Swoole內(nèi)核團(tuán)隊(duì)開設(shè)的專欄,會(huì)逐漸投入精力寫文章介紹Swoole的開發(fā)歷程,實(shí)現(xiàn)原理,應(yīng)用實(shí)踐等,大家可以更好的交流,共同學(xué)習(xí),建設(shè)PHP生態(tài)。 協(xié)程調(diào)度 去年Swoole推出了4.0版本后,完整...

    Bowman_han 評論0 收藏0
  • 【generator101】 - generator是一種顯式控制的協(xié)程

    摘要:協(xié)程其實(shí)就是一個(gè)可中途中斷,由外部來控制執(zhí)行進(jìn)程的函數(shù)。這些第三方的選擇的共同特點(diǎn)是協(xié)程的都是隱式的。這就是顯示控制和隱式控制的區(qū)別。本文討論的協(xié)程就是這一種,后面會(huì)逐漸展開到如何利用這種顯示控制的協(xié)程來解決阻塞和流程阻塞的問題。 Python官方的實(shí)現(xiàn)里,協(xié)程只有g(shù)enerator這一招。協(xié)程其實(shí)就是一個(gè)可中途中斷,由外部來控制執(zhí)行進(jìn)程的函數(shù)。除了官方的generator,還有很多第...

    894974231 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<