国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > PHP > 正文

PHP的多任務協程處理的分析

2020-03-22 18:23:03
字體:
來源:轉載
供稿:網友
這篇文章主要介紹了關于PHP的多任務協程處理,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

那么,開始吧!

3615091407-5b4d4c9d0f16b_articlex[1].jpg

這就是本文我們要討論的問題。不過我們會從更簡單更熟悉的示例開始。

一切從數組開始

我們可以通過簡單的遍歷來使用數組:

$array = [ foo , bar , baz foreach ($array as $key = $html' target='_blank'>value) { print item: . $key . | . $value . /n for ($i = 0; $i count($array); $i++) { print item: . $i . | . $array[$i] . /n }

這是我們日常編碼所依賴的基本實現。可以通過遍歷數組獲取每個元素的鍵名和鍵值。

當然,如果我們希望能夠知道在何時可以使用數組。PHP 提供了一個方便的內置函數:

print is_array($array) ? yes : no // yes
類數組處理

有時,我們需要對一些數據使用相同的方式進行遍歷處理,但它們并非數組類型。比如對 DOMDocument 類進行處理:

$document = new DOMDocument();$document- loadXML( p /p $elements = $document- getElementsByTagName( p print_r($elements); // DOMNodeList Object ( [length] = 1 )

這顯然不是一個數組,但是它有一個 length 屬性。我們能像遍歷數組一樣,對其進行遍歷么?我們可以判斷它是否實現了下面這個特殊的接口:

print ($elements instanceof Traversable) ? yes : no // yes

這真的太有用了。它不會導致我們在遍歷非可遍歷數據時觸發錯誤。我們僅需在處理前進行檢測即可。

不過,這會引發另外一個問題:我們能否讓自定義類也擁有這個功能呢?回答是肯定的!第一個實現方法類似如下:

class MyTraversable implements Traversable // 在這里編碼...}

如果我們執行這個類,我們將看到一個錯誤信息:

PHP Fatal error: Class MyTraversable must implement interface Traversable as part of either Iterator or IteratorAggregateIterator(迭代器)

我們無法直接實現 Traversable,但是我們可以嘗試第二種方案:

class MyTraversable implements Iterator // 在這里編碼...}

這個接口需要我們實現 5 個方法。讓我們完善我們的迭代器:

class MyTraversable implements Iterator protected $data; protected $index = 0; public function __construct($data) $this- data = $data; public function current() return $this- data[$this- index]; public function next() return $this- data[$this- index++]; public function key() return $this- index; public function rewind() $this- index = 0; public function valid() return $this- index count($this- data);}

這邊我們需要注意幾個事項:

我們需要存儲構造器方法傳入的 $data 數組,以便后續我們可以從中獲取它的元素。

還需要一個內部索引(或指針)來跟蹤 current 或 next 元素。

rewind() 僅僅重置 index 屬性,這樣 current() 和 next() 才能正常工作。

鍵名并非只能是數字類型!這里使用數組索引是為了保證示例足夠簡單。

我們可以向下面這樣運行這段代碼:

$iterator = new MyIterator([ foo , bar , baz ]);foreach ($iterator as $key = $value) { print item: . $key . | . $value . /n }

這看起來需要處理太多工作,但是這是能夠像數組一樣使用 foreach/for 功能的一個簡潔實現。

IteratorAggregate(聚合迭代器)

還記得第二個接口拋出的 Traversable 異常么?下面看一個比實現 Iterator 接口更快的實現吧:

class MyIteratorAggregate implements IteratorAggregate protected $data; public function __construct($data) $this- data = $data; public function getIterator() return new ArrayIterator($this- data);}

這里我們作弊了。相比于實現一個完整的 Iterator,我們通過 ArrayIterator() 裝飾。不過,這相比于通過實現完整的 Iterator 簡化了不少代碼。

4232843658-5b4d4c9d111eb_articlex[1].jpg

兄弟莫急!先讓我們比較一些代碼。首先,我們在不使用生成器的情況下從文件中讀取每一行數據:

$content = file_get_contents(__FILE__);$lines = explode( /n , $content);foreach ($lines as $i = $line) { print $i . . . $line . /n }

這段代碼讀取文件自身,然后會打印出每行的行號和代碼。那么為什么我們不使用生成器呢!

function lines($file) { $handle = fopen($file, r  while (!feof($handle)) { yield trim(fgets($handle)); fclose($handle);foreach (lines(__FILE__) as $i = $line) { print $i . . . $line . /n }

我知道這看起來更加復雜。不錯,不過這是因為我們沒有使用 file_get_contents() 函數。一個生成器看起來就像是一個函數,但是它會在每次獲取到 yield 關鍵詞是停止運行。

生成器看起來有點像迭代器:

print_r(lines(__FILE__)); // Generator Object ( )

盡管它不是迭代器,它是一個 Generator。它的內部定義了什么方法呢?

print_r(get_class_methods(lines(__FILE__)));// Array// [0] = rewind// [1] = valid// [2] = current// [3] = key// [4] = next// [5] = send// [6] = throw// [7] = __wakeup// )
如果你讀取一個大文件,然后使用 memory_get_peak_usage(),你會注意到生成器的代碼會使用固定的內存,無論這個文件有多大。它每次進度去一行。而是用 file_get_contents() 函數讀取整個文件,會使用更大的內存。這就是在迭代處理這類事物時,生成器的能給我們帶來的優勢!Send(發送數據)

可以將數據發送到生成器中。看下下面這個生成器:

 ?php$generator = call_user_func(function() { yield foo print $generator- current() . /n // foo
注意這里我們如何在 call_user_func() 函數中封裝生成器函數的?這里僅僅是一個簡單的函數定義,然后立即調用它獲取一個新的生成器實例...

我們已經見過 yield 的用法。我們可以通過擴展這個生成器來接收數據:

$generator = call_user_func(function() { $input = (yield foo  print inside: . $input . /n print $generator- current() . /n $generator- send( bar 

數據通過 yield 關鍵字傳入和返回。首先,執行 current() 代碼直到遇到 yield,返回 foo。send() 將輸出傳入到生成器打印輸入的位置。你需要習慣這種用法。

拋出異常(Throw)

由于我們需要同這些函數進行交互,可能希望將異常推送到生成器中。這樣這些函數就可以自行處理異常。

看看下面這個示例:

$multiply = function($x, $y) { yield $x * $y;print $multiply(5, 6)- current(); // 30

現在讓我們將它封裝到另一個函數中:

$calculate = function ($op, $x, $y) use ($multiply) { if ($op === multiply ) { $generator = $multiply($x, $y); return $generator- current();print $calculate( multiply , 5, 6); // 30

這里我們通過一個普通閉包將乘法生成器封裝起來。現在讓我們驗證無效參數:

$calculate = function ($op, $x, $y) use ($multiply) { if ($op === multiply ) { $generator = $multiply($x, $y); if (!is_numeric($x) || !is_numeric($y)) { throw new InvalidArgumentException(); return $generator- current();print $calculate( multiply , 5, foo // PHP Fatal error...

如果我們希望能夠通過生成器處理異常?我們怎樣才能將異常傳入生成器呢!

$multiply = function ($x, $y) { try { yield $x * $y; } catch (InvalidArgumentException $exception) { print ERRORS! $calculate = function ($op, $x, $y) use ($multiply) { if ($op === multiply ) { $generator = $multiply($x, $y); if (!is_numeric($x) || !is_numeric($y)) { $generator- throw(new InvalidArgumentException()); return $generator- current();print $calculate( multiply , 5, foo // PHP Fatal error...

棒呆了!我們不僅可以像迭代器一樣使用生成器。還可以通過它們發送數據并拋出異常。它們是可中斷和可恢復的函數。有些語言把這些函數叫做……

405043990-5b4d4c9d0d35a_articlex[1].jpg

我們可以使用協程(coroutines)來構建異步代碼。讓我們來創建一個簡單的任務調度程序。首先我們需要一個 Task 類:

class Task protected $generator; public function __construct(Generator $generator) $this- generator = $generator; public function run() $this- generator- next(); public function finished() return !$this- generator- valid();}

Task 是普通生成器的裝飾器。我們將生成器賦值給它的成員變量以供后續使用,然后實現一個簡單的 run() 和 finished() 方法。run() 方法用于執行任務,finished() 方法用于讓調度程序知道何時終止運行。

然后我們需要一個 Scheduler 類:

class Scheduler protected $queue; public function __construct() $this- queue = new SplQueue(); public function enqueue(Task $task) $this- queue- enqueue($task); pulic function run() while (!$this- queue- isEmpty()) { $task = $this- queue- dequeue(); $task- run(); if (!$task- finished()) { $this- queue- enqueue($task);}

Scheduler 用于維護一個待執行的任務隊列。run() 會彈出隊列中的所有任務并執行它,直到運行完整個隊列任務。如果某個任務沒有執行完畢,當這個任務本次運行完成后,我們將再次入列。

SplQueue 對于這個示例來講再合適不過了。它是一種 FIFO(先進先出:fist in first out) 數據結構,能夠確保每個任務都能夠獲取足夠的處理時間。

我們可以像這樣運行這段代碼:

$scheduler = new Scheduler();$task1 = new Task(call_user_func(function() { for ($i = 0; $i $i++) { print task1: . $i . /n  yield;$task2 = new Task(call_user_func(function() { for ($i = 0; $i $i++) { print task2: . $i . /n  yield;$scheduler- enqueue($task1);$scheduler- enqueue($task2);$scheduler- run();

運行時,我們將看到如下執行結果:

task 1: 0task 1: 1task 2: 0task 2: 1task 1: 2task 2: 2task 2: 3task 2: 4task 2: 5

這幾乎就是我們想要的執行結果。不過有個問題發生在首次運行每個任務時,它們都執行了兩次。我們可以對 Task 類稍作修改來修復這個問題:

class Task protected $generator; protected $run = false; public function __construct(Generator $generator) $this- generator = $generator; public function run() if ($this- run) { $this- generator- next(); } else { $this- generator- current(); $this- run = true; public function finished() return !$this- generator- valid();}

我們需要調整首次 run() 方法調用,從生成器當前有效的指針讀取運行。后續調用可以從下一個指針讀取運行...

3991232520-5b4d4c9d0f4b0_articlex[1].jpg

有些人基于這個思路實現了一些超贊的類庫。我們來看看其中的兩個...

RecoilPHP

RecoilPHP 是一套基于協程的類庫,它最令人印象深刻的是用于 ReactPHP 內核。可以將事件循環在 RecoilPHP 和 RecoilPHP 之間進行交換,而你的程序無需架構上的調整。

我們來看一下 ReactPHP 異步 DNS 解決方案:

function resolve($domain, $resolver) { $resolver - resolve($domain) - then(function ($ip) use ($domain) { print domain: . $domain . /n  print ip: . $ip . /n  }, function ($error) {  print $error . /n function run() $loop = React/EventLoop/Factory::create(); $factory = new React/Dns/Resolver/Factory(); $resolver = $factory- create( 8.8.8.8 , $loop); resolve( silverstripe.org , $resolver); resolve( wordpress.org , $resolver); resolve( wardrobecms.com , $resolver); resolve( pagekit.com , $resolver); $loop- run();run();

resolve() 接收域名和 DNS 解析器,并使用 ReactPHP 執行標準的 DNS 查找。不用太過糾結與 resolve() 函數內部。重要的是這個函數不是生成器,而是一個函數!

run() 創建一個 ReactPHP 事件循環,DNS 解析器(這里是個工廠實例)解析若干域名。同樣,這個也不是一個生成器。

想知道 RecoilPHP 到底有何不同?還希望掌握更多細節!

use Recoil/Recoil;function resolve($domain, $resolver) try { $ip = (yield $resolver- resolve($domain)); print domain: . $domain . /n  print ip: . $ip . /n  } catch (Exception $exception) { print $exception- getMessage() . /n function run() $loop = (yield Recoil::eventLoop()); $factory = new React/Dns/Resolver/Factory(); $resolver = $factory- create( 8.8.8.8 , $loop); yield [ resolve( silverstripe.org , $resolver), resolve( wordpress.org , $resolver), resolve( wardrobecms.com , $resolver), resolve( pagekit.com , $resolver),Recoil::run( run 

通過將它集成到 ReactPHP 來完成一些令人稱奇的工作。每次運行 resolve() 時,RecoilPHP 會管理由 $resoler- resolve() 返回的 promise 對象,然后將數據發送給生成器。此時我們就像在編寫同步代碼一樣。與我們在其他一步模型中使用回調代碼不同,這里只有一個指令列表。

RecoilPHP 知道它應該管理一個有執行 run() 函數時返回的 yield 數組。RoceilPHP 還支持基于協程的數據庫(PDO)和日志庫。

IcicleIO

IcicleIO 為了一全新的方案實現 ReactPHP 一樣的目標,而僅僅使用協程功能。相比 ReactPHP 它僅包含極少的組件。但是,核心的異步流、服務器、Socket、事件循環特性一個不落。

讓我們看一個 socket 服務器示例:

use Icicle/Coroutine/Coroutine;use Icicle/Loop/Loop;use Icicle/Socket/Client/ClientInterface;use Icicle/Socket/Server/ServerInterface;use Icicle/Socket/Server/ServerFactory;$factory = new ServerFactory();$coroutine = Coroutine::call(function (ServerInterface $server) { $clients = new SplObjectStorage(); $handler = Coroutine::async( function (ClientInterface $client) use ( $clients) { $clients- attach($client); $host = $client- getRemoteAddress(); $port = $client- getRemotePort(); $name = $host . : . $port; try { foreach ($clients as $stream) { if ($client !== $stream) { $stream- write($name . connected./n  yield $client- write( Welcome . $name . !/n  while ($client- isReadable()) { $data = trim(yield $client- read()); if ( /exit === $data) { yield $client- end( Goodbye!/n  } else { $message = $name . : . $data . /n  foreach ($clients as $stream) { if ($client !== $stream) { $stream- write($message); } catch (Exception $exception) { $client- close($exception); } finally { $clients- detach($client); foreach ($clients as $stream) { $stream- write($name . disconnected./n  while ($server- isOpen()) { $handler(yield $server- accept());}, $factory- create( 127.0.0.1 , 6000));Loop::run();

據我所知,這段代碼所做的事情如下:

在 127.0.0.1 和 6000 端口創建一個服務器實例,然后將其傳入外部生成器.

外部生成器運行,同時服務器等待新連接。當服務器接收一個連接它將其傳入內部生成器。

內部生成器寫入消息到 socket。當 socket 可讀時運行。

每次 socket 向服務器發送消息時,內部生成器檢測消息是否是退出標識。如果是,通知其他 socket。否則,其它 socket 發送這個相同的消息。

打開命令行終端輸入 nc localhost 6000 查看執行結果!

該示例使用 SplObjectStorage 跟蹤 socket 連接。這樣我們就可以向所有 socket 發送消息。

4267668675-5b4d4c9d11821_articlex[1].jpg

這個話題可以包含很多內容。希望您能看到生成器是如何創建的,以及它們如何幫助編寫迭代程序和異步代碼。

如果你有問題,可以隨時問我。

相關推薦:

淺談一下PHP生成器的使用方法

php實現多進程 多任務的一例代碼

以上就是PHP的多任務協程處理的分析的詳細內容,PHP教程

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 灵川县| 特克斯县| 陆丰市| 宜章县| 元朗区| 通道| 苏尼特左旗| 太康县| 兴国县| 文成县| 义马市| 龙州县| 儋州市| 和林格尔县| 靖州| 岳阳市| 潍坊市| 湄潭县| 建昌县| 从化市| 宜兰县| 四川省| 灵武市| 淄博市| 利川市| 深圳市| 军事| 武平县| 行唐县| 蒲城县| 化德县| 资阳市| 汉中市| 永川市| 宝丰县| 东兴市| 游戏| 微山县| 富平县| 南岸区| 湟源县|