PHP中使用協(xié)同程序?qū)崿F(xiàn)合作多任務(wù)第1/2頁(7)_PHP教程
推薦:PHP中使用協(xié)同程序?qū)崿F(xiàn)合作多任務(wù)PHP5.5一個(gè)比較好的新功能是實(shí)現(xiàn)對生成器和協(xié)同程序的支持。對于生成器,PHP的文檔和各種其他的博客文章(就像這一個(gè)或這一個(gè))已經(jīng)有了非常詳細(xì)的講解。協(xié)同程序相對受到的關(guān)注就少了,所以協(xié)同程序雖然有很強(qiáng)大的功能但也很難被知曉,解釋起來也比較困難。 這篇文章
這段代碼試圖把重復(fù)循環(huán)“輸出n次“的代碼嵌入到一個(gè)獨(dú)立的協(xié)程里,然后從主任務(wù)里調(diào)用它。然而它無法運(yùn)行。正如在這篇文章的開始 所提到的,調(diào)用生成器(或者協(xié)程)將沒有真正地做任何事情,它僅僅返回一個(gè)對象。這也出現(xiàn)在上面的例子里。echoTimes調(diào)用除了放回一個(gè)(無用的) 協(xié)程對象外不做任何事情。
為了仍然允許這么做,我們需要在這個(gè)裸協(xié)程上寫一個(gè)小小的封裝。我們將調(diào)用它:“協(xié)程堆棧”。因?yàn)樗鼘⒐芾砬短椎膮f(xié)程調(diào)用堆棧。 這將是通過生成協(xié)程來調(diào)用子協(xié)程成為可能:
$retval = (yield someCoroutine($foo, $bar));
使用yield,子協(xié)程也能再次返回值:
yield retval("I'm a return value!");
retval函數(shù)除了返回一個(gè)值的封裝外沒有做任何其他事情。這個(gè)封裝將表示它是一個(gè)返回值。
復(fù)制代碼 代碼如下:<?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é)程轉(zhuǎn)變?yōu)閰f(xié)程堆棧(它支持子調(diào)用),我們將不得不編寫另外一個(gè)函數(shù)(很明顯,它是另一個(gè)協(xié)程):
復(fù)制代碼 代碼如下:<?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);
}
}
這 個(gè)函數(shù)在調(diào)用者和當(dāng)前正在運(yùn)行的子協(xié)程之間扮演著簡單代理的角色。在$gen->send(yield $gen->key()=>$value);這行完成了代理功能。另外它檢查返回值是否是生成器,萬一是生成器的話,它將開始運(yùn)行這個(gè)生成 器,并把前一個(gè)協(xié)程壓入堆棧里。一旦它獲得了CoroutineReturnValue的話,它將再次請求堆棧彈出,然后繼續(xù)執(zhí)行前一個(gè)協(xié)程。
為了使協(xié)程堆棧在任務(wù)里可用,任務(wù)構(gòu)造器里的$this-coroutine =$coroutine;這行需要替代為$this->coroutine = StackedCoroutine($coroutine);。
現(xiàn)在我們可以稍微改進(jìn)上面web服務(wù)器例子:把wait+read(和wait+write和warit+accept)這樣的動(dòng)作分組為函數(shù)。為了分組相關(guān)的 功能,我將使用下面類: 復(fù)制代碼 代碼如下:
<?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)在服務(wù)器可以編寫的稍微簡潔點(diǎn)了: 復(fù)制代碼 代碼如下:
<?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();
}
錯(cuò)誤處理
作為一個(gè)優(yōu)秀的程序員,相信你已經(jīng)察覺到上面的例子缺少錯(cuò)誤處理。幾乎所有的 socket 都是易出錯(cuò)的。我這樣做的原因一方面固然是因?yàn)殄e(cuò)誤處理的乏味(特別是 socket�。硪环矫嬉苍谟谒苋菀资勾a體積膨脹。
不過,我仍然了一講一下常見的協(xié)程錯(cuò)誤處理:協(xié)程允許使用 throw() 方法在其內(nèi)部拋出一個(gè)錯(cuò)誤。盡管此方法還未在 PHP 中實(shí)現(xiàn),但我很快就會(huì)提交它,就在今天。
throw() 方法接受一個(gè) Exception,并將其拋出到協(xié)程的當(dāng)前懸掛點(diǎn),看看下面代碼: 復(fù)制代碼 代碼如下:
<?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"
這非常棒,因?yàn)槲覀兛梢允褂孟到y(tǒng)調(diào)用以及子協(xié)程調(diào)用異常拋出。對與系統(tǒng)調(diào)用,Scheduler::run() 方法需要一些小調(diào)整: 復(fù)制代碼 代碼如下:
<?php
if ($retval instanceof SystemCall) {
try {
$retval($task, $this);
} catch (Exception $e) {
$task->setException($e);
$this->schedule($task);
}
continue;
}
Task 類也許要添加 throw 調(diào)用處理: 復(fù)制代碼 代碼如下:
<?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)在,我們已經(jīng)可以在系統(tǒng)調(diào)用中使用異常拋出了!例如,要調(diào)用 killTask,讓我們在傳遞 ID 不可用時(shí)拋出一個(gè)異常: 復(fù)制代碼 代碼如下:
<?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!');
}
}
);
}
試試看: 復(fù)制代碼 代碼如下:
<?php
function task() {
try {
yield killTask(500);
} catch (Exception $e) {
echo 'Tried to kill task 500 but failed: ', $e->getMessage(), "\n";
}
}
這些代碼現(xiàn)在尚不能正常運(yùn)作,因?yàn)?stackedCoroutine 函數(shù)無法正確處理異常。要修復(fù)需要做些調(diào)整: 復(fù)制代碼 代碼如下:
<?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;
}
}
}
結(jié)束語
在 這篇文章里,我使用多任務(wù)協(xié)作構(gòu)建了一個(gè)任務(wù)調(diào)度器,其中包括執(zhí)行“系統(tǒng)調(diào)用”,做非阻塞操作和處理錯(cuò)誤。所有這些里真正很酷的事情是任務(wù)的結(jié)果代碼看起 來完全同步,甚至任務(wù)正在執(zhí)行大量的異步操作的時(shí)候也是這樣。如果你打算從套接口讀取數(shù)據(jù)的話,你將不需要傳遞某個(gè)回調(diào)函數(shù)或者注冊一個(gè)事件偵聽器。相 反,你只要書寫yield $socket->read()。這兒大部分都是你常常也要編寫的,只在它的前面增加yield。
當(dāng)我第一次 聽到所有這一切的時(shí)候,我發(fā)現(xiàn)這個(gè)概念完全令人折服,而且正是這個(gè)激勵(lì)我在PHP中實(shí)現(xiàn)了它。同時(shí)我發(fā)現(xiàn)協(xié)程真正令人心慌。在令人敬畏的代碼和很大一堆代 碼之間只有單薄的一行,我認(rèn)為協(xié)程正好處在這一行上。講講使用上面所述的方法書寫異步代碼是否真的有益對我來說很難。
無論如何,我認(rèn)為這是一個(gè)有趣的話題,而且我希望你也能找到它的樂趣。歡迎評論:)
分享:php修改NetBeans默認(rèn)字體的大小在Netbeans中由于使用了Swing進(jìn)行開發(fā),所以其中界面的字體也是由Java虛擬機(jī)進(jìn)行配置而不是隨操作系統(tǒng)的。在安裝完Netbeans后默認(rèn)的字體大小是11px。而在Windows下的宋體最小支持12px。所以字體為11px就已經(jīng)無法完整顯示了。 簡單的解決辦法就是將字體改大一點(diǎn)。詳細(xì)的
- PHPNOW安裝Memcached擴(kuò)展方法詳解
- php記錄頁面代碼執(zhí)行時(shí)間
- PHP中獎(jiǎng)概率的抽獎(jiǎng)算法程序代碼
- apache設(shè)置靜態(tài)文件緩存方法介紹
- php對圖像的各種處理函數(shù)代碼小結(jié)
- PHP 關(guān)于訪問控制的和運(yùn)算符優(yōu)先級介紹
- 關(guān)于PHP語言構(gòu)造器介紹
- php/js獲取客戶端mac地址的實(shí)現(xiàn)代碼
- php5.5新數(shù)組函數(shù)array_column使用
- PHP preg_match的匹配多國語言的技巧
- php 中序列化和json使用介紹
- php采集文章中的圖片獲取替換到本地
- 相關(guān)鏈接:
- 教程說明:
PHP教程-PHP中使用協(xié)同程序?qū)崿F(xiàn)合作多任務(wù)第1/2頁(7)
。