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---
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值