PHP中使用協(xié)同程序實現(xiàn)合作多任務(7)_PHP教程
推薦:php修改NetBeans默認字體的大小在Netbeans中由于使用了Swing進行開發(fā),所以其中界面的字體也是由Java虛擬機進行配置而不是隨操作系統(tǒng)的。在安裝完Netbeans后默認的字體大小是11px。而在Windows下的宋體最小支持12px。所以字體為11px就已經無法完整顯示了。 簡單的解決辦法就是將字體改大一點。詳細的
這段代碼試圖把重復循環(huán)“輸出n次“的代碼嵌入到一個獨立的協(xié)程里,然后從主任務里調用它。然而它無法運行。正如在這篇文章的開始 所提到的,調用生成器(或者協(xié)程)將沒有真正地做任何事情,它僅僅返回一個對象。這也出現(xiàn)在上面的例子里。echoTimes調用除了放回一個(無用的) 協(xié)程對象外不做任何事情。
為了仍然允許這么做,我們需要在這個裸協(xié)程上寫一個小小的封裝。我們將調用它:“協(xié)程堆棧”。因為它將管理嵌套的協(xié)程調用堆棧。 這將是通過生成協(xié)程來調用子協(xié)程成為可能:
$retval = (yield someCoroutine($foo, $bar));
使用yield,子協(xié)程也能再次返回值:
yield retval("I'm a return value!");
retval函數除了返回一個值的封裝外沒有做任何其他事情。這個封裝將表示它是一個返回值。
復制代碼 代碼如下:<?php
class CoroutineReturnValue {
protected $value;
public function __construct($value) {
$this->value = $value;
}
public function getValue() {
return $this->value;
}
}
function retval($value) {
return new CoroutineReturnValue($value);
}
為了把協(xié)程轉變?yōu)閰f(xié)程堆棧(它支持子調用),我們將不得不編寫另外一個函數(很明顯,它是另一個協(xié)程):
復制代碼 代碼如下:<?php
function stackedCoroutine(Generator $gen) {
$stack = new SplStack;
for (;;) {
$value = $gen->current();
if ($value instanceof Generator) {
$stack->push($gen);
$gen = $value;
continue;
}
$isReturnValue = $value instanceof CoroutineReturnValue;
if (!$gen->valid() || $isReturnValue) {
if ($stack->isEmpty()) {
return;
}
$gen = $stack->pop();
$gen->send($isReturnValue ? $value->getValue() : NULL);
continue;
}
$gen->send(yield $gen->key() => $value);
}
}
這 個函數在調用者和當前正在運行的子協(xié)程之間扮演著簡單代理的角色。在$gen->send(yield $gen->key()=>$value);這行完成了代理功能。另外它檢查返回值是否是生成器,萬一是生成器的話,它將開始運行這個生成 器,并把前一個協(xié)程壓入堆棧里。一旦它獲得了CoroutineReturnValue的話,它將再次請求堆棧彈出,然后繼續(xù)執(zhí)行前一個協(xié)程。
為了使協(xié)程堆棧在任務里可用,任務構造器里的$this-coroutine =$coroutine;這行需要替代為$this->coroutine = StackedCoroutine($coroutine);。
現(xiàn)在我們可以稍微改進上面web服務器例子:把wait+read(和wait+write和warit+accept)這樣的動作分組為函數。為了分組相關的 功能,我將使用下面類: 復制代碼 代碼如下:
<?php
class CoSocket {
protected $socket;
public function __construct($socket) {
$this->socket = $socket;
}
public function accept() {
yield waitForRead($this->socket);
yield retval(new CoSocket(stream_socket_accept($this->socket, 0)));
}
public function read($size) {
yield waitForRead($this->socket);
yield retval(fread($this->socket, $size));
}
public function write($string) {
yield waitForWrite($this->socket);
fwrite($this->socket, $string);
}
public function close() {
@fclose($this->socket);
}
}
現(xiàn)在服務器可以編寫的稍微簡潔點了: 復制代碼 代碼如下:
<?php
function server($port) {
echo "Starting server at port $port...\n";
$socket = @stream_socket_server("tcp://localhost:$port", $errNo, $errStr);
if (!$socket) throw new Exception($errStr, $errNo);
stream_set_blocking($socket, 0);
$socket = new CoSocket($socket);
while (true) {
yield newTask(
handleClient(yield $socket->accept())
);
}
}
function handleClient($socket) {
$data = (yield $socket->read(8192));
$msg = "Received following request:\n\n$data";
$msgLength = strlen($msg);
$response = <<<RES
HTTP/1.1 200 OK\r
Content-Type: text/plain\r
Content-Length: $msgLength\r
Connection: close\r
\r
$msg
RES;
yield $socket->write($response);
yield $socket->close();
}
錯誤處理
作為一個優(yōu)秀的程序員,相信你已經察覺到上面的例子缺少錯誤處理。幾乎所有的 socket 都是易出錯的。我這樣做的原因一方面固然是因為錯誤處理的乏味(特別是 socket!),另一方面也在于它很容易使代碼體積膨脹。
不過,我仍然了一講一下常見的協(xié)程錯誤處理:協(xié)程允許使用 throw() 方法在其內部拋出一個錯誤。盡管此方法還未在 PHP 中實現(xiàn),但我很快就會提交它,就在今天。
throw() 方法接受一個 Exception,并將其拋出到協(xié)程的當前懸掛點,看看下面代碼: 復制代碼 代碼如下:
<?php
function gen() {
echo "Foo\n";
try {
yield;
} catch (Exception $e) {
echo "Exception: {$e->getMessage()}\n";
}
echo "Bar\n";
}
$gen = gen();
$gen->rewind(); // echos "Foo"
$gen->throw(new Exception('Test')); // echos "Exception: Test"
// and "Bar"
這非常棒,因為我們可以使用系統(tǒng)調用以及子協(xié)程調用異常拋出。對與系統(tǒng)調用,Scheduler::run() 方法需要一些小調整: 復制代碼 代碼如下:
<?php
if ($retval instanceof SystemCall) {
try {
$retval($task, $this);
} catch (Exception $e) {
$task->setException($e);
$this->schedule($task);
}
continue;
}
Task 類也許要添加 throw 調用處理: 復制代碼 代碼如下:
<?php
class Task {
// ...
protected $exception = null;
public function setException($exception) {
$this->exception = $exception;
}
public function run() {
if ($this->beforeFirstYield) {
$this->beforeFirstYield = false;
return $this->coroutine->current();
} elseif ($this->exception) {
$retval = $this->coroutine->throw($this->exception);
$this->exception = null;
return $retval;
} else {
$retval = $this->coroutine->send($this->sendValue);
$this->sendValue = null;
return $retval;
}
}
// ...
}
現(xiàn)在,我們已經可以在系統(tǒng)調用中使用異常拋出了!例如,要調用 killTask,讓我們在傳遞 ID 不可用時拋出一個異常: 復制代碼 代碼如下:
<?php
function killTask($tid) {
return new SystemCall(
function(Task $task, Scheduler $scheduler) use ($tid) {
if ($scheduler->killTask($tid)) {
$scheduler->schedule($task);
} else {
throw new InvalidArgumentException('Invalid task ID!');
}
}
);
}
試試看: 復制代碼 代碼如下:
<?php
function task() {
try {
yield killTask(500);
} catch (Exception $e) {
echo 'Tried to kill task 500 but failed: ', $e->getMessage(), "\n";
}
}
這些代碼現(xiàn)在尚不能正常運作,因為 stackedCoroutine 函數無法正確處理異常。要修復需要做些調整: 復制代碼 代碼如下:
<?php
function stackedCoroutine(Generator $gen) {
$stack = new SplStack;
$exception = null;
for (;;) {
try {
if ($exception) {
$gen->throw($exception);
$exception = null;
continue;
}
$value = $gen->current();
if ($value instanceof Generator) {
$stack->push($gen);
$gen = $value;
continue;
}
$isReturnValue = $value instanceof CoroutineReturnValue;
if (!$gen->valid() || $isReturnValue) {
if ($stack->isEmpty()) {
return;
}
$gen = $stack->pop();
$gen->send($isReturnValue ? $value->getValue() : NULL);
continue;
}
try {
$sendValue = (yield $gen->key() => $value);
} catch (Exception $e) {
$gen->throw($e);
continue;
}
$gen->send($sendValue);
} catch (Exception $e) {
if ($stack->isEmpty()) {
throw $e;
}
$gen = $stack->pop();
$exception = $e;
}
}
}
結束語
在 這篇文章里,我使用多任務協(xié)作構建了一個任務調度器,其中包括執(zhí)行“系統(tǒng)調用”,做非阻塞操作和處理錯誤。所有這些里真正很酷的事情是任務的結果代碼看起 來完全同步,甚至任務正在執(zhí)行大量的異步操作的時候也是這樣。如果你打算從套接口讀取數據的話,你將不需要傳遞某個回調函數或者注冊一個事件偵聽器。相 反,你只要書寫yield $socket->read()。這兒大部分都是你常常也要編寫的,只在它的前面增加yield。
當我第一次 聽到所有這一切的時候,我發(fā)現(xiàn)這個概念完全令人折服,而且正是這個激勵我在PHP中實現(xiàn)了它。同時我發(fā)現(xiàn)協(xié)程真正令人心慌。在令人敬畏的代碼和很大一堆代 碼之間只有單薄的一行,我認為協(xié)程正好處在這一行上。講講使用上面所述的方法書寫異步代碼是否真的有益對我來說很難。
無論如何,我認為這是一個有趣的話題,而且我希望你也能找到它的樂趣。歡迎評論:)
分享:PHP刪除數組中特定元素的兩種方法這篇文章介紹了PHP中刪除數組中特定元素的兩種方法,有需要的朋友可以參考一下 方法一: 復制代碼 代碼如下: ?php $arr1 = array(1,3, 5,7,8); $key = array_search(3, $arr1); if ($key !== false) array_splice($arr1, $key, 1); var_dump($arr1); ? 輸出: array(4)
- 相關鏈接:
- 教程說明:
PHP教程-PHP中使用協(xié)同程序實現(xiàn)合作多任務(7)。