萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> php編程 >> PHP中使用協同程序實現合作多任務第1/2頁

PHP中使用協同程序實現合作多任務第1/2頁

PHP5.5一個比較好的新功能是實現對生成器和協同程序的支持。對於生成器,PHP的文檔和各種其他的博客文章(就像這一個或這一個)已經有了非常詳細的講解。協同程序相對受到的關注就少了,所以協同程序雖然有很強大的功能但也很難被知曉,解釋起來也比較困難。

這篇文章指導你通過使用協同程序來實施任務調度,通過實例實現對技術的理解。我將在前三節做一個簡單的背景介紹。如果你已經有了比較好的基礎,可以直接跳到“協同多任務處理”一節。

生成器

生成器最基本的思想也是一個函數,這個函數的返回值是依次輸出,而不是只返回一個單獨的值。或者,換句話說,生成器使你更方便的實現了迭代器接口。下面通過實現一個xrange函數來簡單說明:

復制代碼 代碼如下:
<?php
function xrange($start, $end, $step = 1) {
    for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
    }
}

foreach (xrange(1, 1000000) as $num) {
    echo $num, "n";
}

上面這個xrange()函數提供了和PHP的內建函數range()一樣的功能。但是不同的是range()函數返回的是一個包含屬組值從1到 100萬的數組(注:請查看手冊)。而xrange()函數返回的是依次輸出這些值的一個迭代器,而且並不會真正以數組形式計算。

這種方法的優點是顯而易見的。它可以讓你在處理大數據集合的時候不用一次性的加載到內存中。甚至你可以處理無限大的數據流。

當然,也可以不同通過生成器來實現這個功能,而是可以通過繼承Iterator接口實現。通過使用生成器實現起來會更方便,而不用再去實現iterator接口中的5個方法了。

生成器為可中斷的函數
要從生成器認識協同程序,理解它們內部是如何工作的非常重要:生成器是可中斷的函數,在它裡面,yield構成了中斷點。 

緊接著上面的例子,如果你調用xrange(1,1000000)的話,xrange()函數裡代碼沒有真正地運行。相反,PHP只是返回了一個實現了迭代器接口的 生成器類實例: 
 

復制代碼 代碼如下:
<?php
$range = xrange(1, 1000000);
var_dump($range); // object(Generator)#1
var_dump($range instanceof Iterator); // bool(true)

你對某個對象調用迭代器方法一次,其中的代碼運行一次。例如,如果你調用$range->rewind(),那麼xrange()裡的代碼運 行到控制流 第一次出現yield的地方。在這種情況下,這就意味著當$i=$start時yield $i才運行。傳遞給yield語句的值是使用$range->current()獲取的。

 為了繼續執行生成器中的代碼,你必須 調用$range->next()方法。這將再次啟動生成器,直到yield語句出現。因此,連續調用next()和current()方法 你將能從生成器裡獲得所有的值,直到某個點沒有再出現yield語句。對xrange()來說,這種情形出現在$i超過$end時。在這中情況下, 控制流將到達函數的終點,因此將不執行任何代碼。一旦這種情況發生,vaild()方法將返回假,這時迭代結束。

協程

協程給上面功能添加的主要東西是回送數據給生成器的能力。這將把生成器到調用者的單向通信轉變為兩者之間的雙向通信。
通過調用生成器的send()方法而不是其next()方法傳遞數據給協程。下面的logger()協程是這種通信如何運行的例子: 

復制代碼 代碼如下:
<?php

function logger($fileName) {
    $fileHandle = fopen($fileName, 'a');
    while (true) {
        fwrite($fileHandle, yield . "n");
    }
}

$logger = logger(__DIR__ . '/log');
$logger->send('Foo');
$logger->send('Bar')

正如你能看到,這兒yield沒有作為一個語句來使用,而是用作一個表達式。即它有一個返回值。yield的返回值是傳遞給send()方法的值。 在這個例子裡,yield將首先返回"Foo",然後返回"Bar"。

上面的例子裡yield僅作為接收者。混合兩種用法是可能的,即既可接收也可發送。接收和發送通信如何進行的例子如下:

復制代碼 代碼如下:
<?php

function gen() {
    $ret = (yield 'yield1');
    var_dump($ret);
    $ret = (yield 'yield2');
    var_dump($ret);
}

$gen = gen();
var_dump($gen->current());    // string(6) "yield1"
var_dump($gen->send('ret1')); // string(4) "ret1"   (the first var_dump in gen)
                              // string(6) "yield2" (the var_dump of the ->send() return value)
var_dump($gen->send('ret2')); // string(4) "ret2"   (again from within gen)
                              // NULL               (the return value of ->send())

馬上理解輸出的精確順序有點困難,因此確定你知道為什按照這種方式輸出。我願意特別指出的有兩點:第一點,yield表達式兩邊使用 圓括號不是偶然。由於技術原因(雖然我已經考慮為賦值增加一個異常,就像Python那樣),圓括號是必須的。第二點,你可能已經注意到 調用current()之前沒有調用rewind()。如果是這麼做的,那麼已經隱含地執行了rewind操作。 

多任務協作

如果閱讀了上面的logger()例子,那麼你認為“為了雙向通信我為什麼要使用協程呢? 為什麼我不能只用常見的類呢?”,你這麼問完全正確。上面的例子演示了基本用法,然而上下文中沒有真正的展示出使用協程的優點。這就是列舉許多協程例子的 理由。正如上面介紹裡提到的,協程是非常強大的概念,不過這樣的應用很稀少而且常常十分復雜。給出一些簡單而真實的例子很難。

在這篇文章裡,我決定去做的是使用協程實現多任務協作。我們盡力解決的問題是你想並發地運行多任務(或者“程序”)。不過處理器在一個時刻只能運行 一個任務(這篇文章的目標是不考慮多核的)。因此處理器需要在不同的任務之間進行切換,而且總是讓每個任務運行 “一小會兒”。 

多任務協作這個術語中的“協作”說明了如何進行這種切換的:它要求當前正在運行的任務自動把控制傳回給調度器,這樣它就可以運行其他任務了。這與 “搶占”多任務相反,搶占多任務是這樣的:調度器可以中斷運行了一段時間的任務,不管它喜歡還是不喜歡。協作多任務在Windows的早期版本 (windows95)和Mac OS中有使用,不過它們後來都切換到使用搶先多任務了。理由相當明確:如果你依靠程序自動傳回 控制的話,那麼壞行為的軟件將很容易為自身占用整個CPU,不與其他任務共享。 

這個時候你應當明白協程和任務調度之間的聯系:yield指令提供了任務中斷自身的一種方法,然後把控制傳遞給調度器。因此協程可以運行多個其他任務。更進一步來說,yield可以用來在任務和調度器之間進行通信。

我們的目的是 對 “任務”用更輕量級的包裝的協程函數:
 

復制代碼 代碼如下:
<?php

class Task {
    protected $taskId;
    protected $coroutine;
    protected $sendValue = null;
    protected $beforeFirstYield = true;

    public function __construct($taskId, Generator $coroutine) {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
    }

    public function getTaskId() {
        return $this->taskId;
    }

    public function setSendValue($sendValue) {
        $this->sendValue = $sendValue;
    }

    public function run() {
        if ($this->beforeFirstYield) {
            $this->beforeFirstYield = false;
            return $this->coroutine->current();
        } els

copyright © 萬盛學電腦網 all rights reserved