PHP 多线程扩展parallel(二)
parallel\Runtime
- 每个运行时代表一个PHP线程,该线程在构建时创建(并引导)。然后线程等待任务被调度:调度的任务将被执行FIFO,然后线程继续等待,直到更多的任务被调度,或者被PHP对象的正常作用域规则关闭、终止或销毁。
- 创建新的运行时时,它不会与创建它的线程(或进程)共享代码。这意味着它没有加载相同的类和函数,也没有设置相同的自动加载器。在某些情况下,需要非常轻量级的运行时,因为将要调度的任务不需要访问父线程中的代码。在那些任务确实需要访问相同代码的情况下,将自动加载器设置为引导程序就足够了。
- close 在调用此方法后,主线程在子线程执行完成后关闭
- kill 在调用此方法后,主线程立即关闭子线程
final class parallel\Runtime {
/* Create */
public __construct()
public __construct(string $bootstrap)
/* Execute */
public run(Closure $task): ?Future
public run(Closure $task, array $argv): ?Future
/* Join */
public close(): void
public kill(): void
}
示例
1.基础示例
<?php
use parallel\Runtime;
$function = function(string $str){
echo $str . "\n";
};
# 无引导文件
$r1 = new Runtime();
$r3 = new Runtime();
# 有引导文件
$r2 = new Runtime('function.php');
# 匿名函数
$r1->run(function(){
sleep(1);
# test(); 此处调用会抛出 undefined function test() 异常
echo 'r1' . "\n";
});
# 线程未关闭前,可多次调用run方法 FIFO
$r1->run(function(){
sleep(1);
# test(); 此处调用会抛出 undefined function test() 异常
echo 'r1' . "\n";
});
$r2->run(function(){
test();
});
# 带有参数的函数调用
$r3->run($function,['r3']);
<?php
# function.php
function test(){
echo "I'm function \n";
}
2.close与kill
<?php
use parallel\Runtime;
$r1 = new Runtime();
$r1->run(function(){
sleep(2);
echo "r1 \n";
});
# close kill 仅可调用一次,否则抛出异常
$r1->close(); # 在等待2s后,输出 r1
# $r1->kill(); # 线程即刻退出,程序执行完毕
# 线程关闭后,不可再调用run方法 此调用将抛出异常
$r1->run(function(){
sleep(2);
echo "r1 \n";
});
parallel\Future
- Future表示任务的返回值或未捕获的异常,并暴露用于取消的API
- value() 获取任务的返回值,阻塞进程直到任务执行完成
- cancelled() 检测任务是否被取消
- done() 检测任务是否执行完成
- cancel() 取消任务。执行此方法后,不可再调用 value(),cancelled()返回true,done()返回true
final class parallel\Future {
/* Resolution */
public value(): mixed
/* State */
public cancelled(): bool
public done(): bool
/* Cancellation */
public cancel(): bool
}
示例
<?php
use parallel\{Runtime,Future};
$r1 = new Runtime();
$f = $r1->run(function(){
sleep(2);
return 'hello future';
});
var_dump($f->done()); // false
var_dump($f->value());// 2s后输出
var_dump($f->done());// true
<?php
use parallel\{Runtime,Future};
$r1 = new Runtime();
$f = $r1->run(function(){
sleep(2);
return 'hello future';
});
var_dump($f->done());// false
var_dump($f->cancelled()); // false
$f->cancel();
var_dump($f->cancelled()); // true
var_dump($f->done());// true
parallel\Channel
- 可创建有缓冲与无缓冲两种channel
- 无缓冲channel,没有\channel::recv(),调用\channel::send()将会阻塞
- 无缓冲channel,调用\channel::recv(),将会阻塞到有数据发出
- 无缓冲通道不仅是任务间共享数据的一种方式,也是一种简单的同步方法。无缓冲通道是任务间共享数据的最快方式,需要最少的复制。
- 有缓冲channel,在达到容量之前,没有\channel::recv(),调用\channel::send()将不会阻塞
- 有缓冲channel,调用\channel::recv(),将会阻塞到缓冲区有数据
final class parallel\Channel {
/* Anonymous Constructor */
public __construct()
public __construct(int $capacity)
/* Access */
public make(string $name): Channel
public make(string $name, int $capacity): Channel
public open(string $name): Channel
/* Sharing */
public recv(): mixed
public send(mixed $value): void
/* Closing */
public close(): void
/* Constant for Infinitely Buffered */
const Infinite;
}
示例
<?php
use parallel\{Runtime,Future,Channel};
$ch = new Channel();
$r1 = new Runtime();
$r2 = new Runtime();
$r1->run(function(Channel $ch){
$ch->send('hello channel');
},[$ch]);
$r2->run(function(Channel $ch){
echo "I'm waiting \n";
echo $ch->recv();
},[$ch]);
<?php
use parallel\{Runtime,Future,Channel};
$ch = new Channel();
$r1 = new Runtime();
$r2 = new Runtime();
# 程序将阻塞
$r1->run(function(Channel $ch){
$ch->send('hello channel');
},[$ch]);
<?php
use parallel\{Runtime,Future,Channel};
$ch = new Channel();
$r1 = new Runtime();
$r2 = new Runtime();
# 程序将阻塞
$r2->run(function(Channel $ch){
echo $ch->recv();
},[$ch]);
<?php
use parallel\{Runtime,Channel};
$ch = new Channel(2); // 有缓冲channel
$r1 = new Runtime();
$r2 = new Runtime();
# 输出123后阻塞
$f = $r1->run(function(Channel $ch){
echo 1;
$ch->send('hello channel');
echo 2;
$ch->send('hello channel');
echo 3;
$ch->send('hello channel');
echo 4;
$ch->send('hello channel');
},[$ch]);
<?php
use parallel\{Runtime,Channel};
$ch = Channel::make('ch1');
$r1 = new Runtime();
# 不需要传入Channel实例,通过名称获取
$r1->run(function(){
echo Channel::open('ch1')->recv();
},[$ch]);
$ch->send('hello');
parallel\Events parallel\Events\Input parallel\Events\Event parallel\Events\Event\Type
# Event Loop 监视一组future对象和/或channel(target)的状态,以便在目标变为可用并且可以执行操作而不会阻塞事件循环时,执行读取(parallel\Future::value(),parallel\Channel::recv())和写入(parallel\Channel::send())操作
final class parallel\Events implements Countable, Traversable {
/* Input 设置向channel写入的数据*/
public setInput(Input $input): void
/* Targets */
public addChannel(parallel\Channel $channel): void
public addFuture(string $name, parallel\Future $future): void
public remove(string $target): void
/* Behaviour */
public setBlocking(bool $blocking): void
public setTimeout(int $timeout): void
/* Polling */
public poll(): ?parallel\Events\Event
}
# 一个 Input 对象是一个数据容器,当数据可用时,parallel\Events 对象将把数据写入 parallel\Channel 对象中。多个事件循环可以共享一个 Input 容器 - parallel 在将其设置为 parallel\Events 对象的输入时不会验证容器的内容。
final class parallel\Events\Input {
public add(string $target, mixed $value): void
public remove(string $target): void
public clear(): void
}
final class parallel\Events\Event {
/* Shall be one of Event\Type constants */
public int $type;
/* Shall be the source of the event (target name) */
public string $source;
/* Shall be either Future or Channel */
public object $object;
/* Shall be set for Read/Error events */
public $value;
}
final class parallel\Events\Event\Type {
/* Event::$object was read into Event::$value 1 */
const Read;
/* Input for Event::$source written to Event::$object 2 */
const Write;
/* Event::$object (Channel) was closed 3 */
const Close;
/* Event::$object (Future) was cancelled 5 */
const Cancel;
/* Runtime executing Event::$object (Future) was killed 6 */
const Kill;
/* Event::$object (Future) raised error 4 */
const Error;
}
示例
<?php
use parallel\{Runtime,Channel,Events};
use parallel\Events\Input;
$ch1 = Channel::make('ch1');
$ch2 = Channel::make('ch2');
$r1 = new Runtime();
$r2 = new Runtime();
$r3 = new Runtime();
$r1->run(function($ch){
for($i=0;$i<5;$i++){
$ch->send('ch1-'.$i);
sleep(2);
}
},[$ch1]);
$r2->run(function($ch){
for($i=0;$i<5;$i++){
$ch->send('ch2-'.$i);
sleep(2);
}
},[$ch2]);
$f1 = $r3->run(function(){
return 'future';
});
$events = new Events();
$events->setBlocking(false);
$events->addChannel($ch1);
$events->addChannel($ch2);
$events->addFuture('f1',$f1);
while(true){
$event = $events->poll();
if ($event){
$source = $event->source;
// var_dump($source); # source 此处为channel名称
// var_dump($event->type); # 此处值为1 表示为read操作
var_dump($event->value);
if($event->object instanceof parallel\Channel){
$events->addChannel($$source); # 获取event后,会弹出此event,如果需要继续监听需要再次add
}elseif($event->object instanceof parallel\Future){
// $events->addFuture($source,$f1); # 获取event后,会弹出此event,如果需要继续监听需要再次add
}
}
}
# 打印如下内容
// string(6) "future"
// string(5) "ch1-0"
// string(5) "ch2-0"
// string(5) "ch1-1"
// string(5) "ch2-1"
// string(5) "ch1-2"
// string(5) "ch2-2"
// string(5) "ch1-3"
// string(5) "ch2-3"
// string(5) "ch1-4"
// string(5) "ch2-4"
<?php
use parallel\{Runtime,Channel,Events};
use parallel\Events\Input;
$ch1 = Channel::make('ch1',2);
$events = new Events();
$input = new Input();
$input->add('ch1','hello');
$events->setInput($input);
$events->addChannel($ch1);
$events->poll();
$r1 = new Runtime();
$r1->run(function($ch){
echo $ch->recv();
},[$ch1]);
<?php
use parallel\{Runtime,Channel,Events};
use parallel\Events\Input;
$ch1 = Channel::make('ch1',2);
$r1 = new Runtime();
$r1->run(function($ch){
echo 'runtime-'.$ch->recv(); // 此处不会打印
},[$ch1]);
$events = new Events();
$input = new Input();
$input->add('ch1','hello');
$events->setInput($input);
$events->addChannel($ch1);
while(true){
$event = $events->poll();
if ($event){
$source = $event->source;
// var_dump($source); # source 此处为channel名称
var_dump($event->type); # 此处值为1 表示为read操作;2 表示为write操作
var_dump($event->value);
if($event->object instanceof parallel\Channel){
$events->addChannel($$source); # 获取event后,会弹出此event,如果需要继续监听需要再次add
}elseif($event->object instanceof parallel\Future){
// $events->addFuture($source,$f1); # 获取event后,会弹出此event,如果需要继续监听需要再次add
}
}
}
# 打印内容如下
// int(2)
// NULL
// int(1)
// string(5) "hello"
parallel\Sync
- Parallel\Sync类提供对最基本的同步操作(互斥锁、条件变量)的访问,并允许实现信号量。对于大多数应用程序,使用通道实现更好,但在某些情况下,可用于底层开发人员访问底层方法
示例
<?php
$sync = new \parallel\Sync;
\parallel\run(function() use($sync){
echo '---waiting---' ."\n";
$sync(function() use($sync) {
while($sync->get()!=1){
$sync->wait();
}
});
echo '---finish---' ."\n";
});
\parallel\run(function() use($sync) {
sleep(1);
echo '---notify---' ."\n";
$sync->set(1);
$sync->notify(true);
});
# 输出如下
// ---waiting---
// ---notify---
// ---finish---