首页 » SEO优化 » PHP对子技巧_你知道PHP协程是什么吗

PHP对子技巧_你知道PHP协程是什么吗

duote123 2024-11-21 0

扫一扫用手机浏览

文章目录 [+]

先搞清楚,什么是协程。

你可能已经听过『进程』和『线程』这两个观点。

PHP对子技巧_你知道PHP协程是什么吗

进程便是二进制可实行文件在打算机内存里的一个运行实例,就好比你的.exe文件是个类,进程便是new出来的那个实例。

PHP对子技巧_你知道PHP协程是什么吗
(图片来自网络侵删)

进程是打算机系统进行资源分配和调度的基本单位(调度单位这里别纠结线程进程的),每个CPU下同一时候只能处理一个进程。

所谓的并行,只不过是看起来并行,CPU事实上在用很快的速率切换不同的进程。

进程的切换须要进行系统调用,CPU要保存当提高程的各个信息,同时还会使CPUCache被废掉。

以是进程切换不到费不得已就不做。

那么怎么实现『进程切换不到费不得已就不做』呢?

首先进程被切换的条件是:进程实行完毕、分配给进程的CPU韶光片结束,系统发生中断须要处理,或者进程等待必要的资源(进程壅塞)等。
你想下,前面几种情形自然没有什么话可说,但是如果是在壅塞等待,是不是就摧残浪费蹂躏了。

实在壅塞的话我们的程序还有其他可实行的地方可以实行,不一定要傻傻的等!
以是就有了线程。
线程大略理解便是一个『微进程』,专门跑一个函数(逻辑流)。

以是我们就可以在编写程序的过程中将可以同时运行的函数用线程来表示了。

线程有两种类型,一种是由内核来管理和调度。

我们说,只要涉及须要内核参与管理调度的,代价都是很大的。
这种线程实在也就办理了当一个进程中,某个正在实行的线程碰着壅塞,我们可以调度其余一个可运行的线程来跑,但是还是在同一个进程里,以是没有了进程切换。

还有其余一种线程,他的调度是由程序员自己写程序来管理的,对内核来说不可见。
这种线程叫做『用户空间线程』。

协程可以理解便是一种用户空间线程。

协程,有几个特点:

协同,由于是由程序员自己写的调度策略,其通过协作而不是抢占来进行切换在用户态完成创建,切换和销毁⚠️ 从编程角度上看,协程的思想实质上便是掌握流的主动让出(yield)和规复(resume)机制迭代器常常用来实现协程

说到这里,你该当明白协程的基本观点了吧?

PHP实现协程

一步一步来,从阐明观点提及!

可迭代工具

PHP5供应了一种定义工具的方法使其可以通过单元列表来遍历,例如用foreach语句。

你如果要实现一个可迭代工具,你就要实现Iterator接口:

<?phpclassMyIteratorimplementsIterator{private$var=array();publicfunction__construct($array){if(is_array($array)){$this->var=$array;}}publicfunctionrewind(){echo"rewinding\n";reset($this->var);}publicfunctioncurrent(){$var=current($this->var);echo"current:$var\n";return$var;}publicfunctionkey(){$var=key($this->var);echo"key:$var\n";return$var;}publicfunctionnext(){$var=next($this->var);echo"next:$var\n";return$var;}publicfunctionvalid(){$var=$this->current()!==false;echo"valid:{$var}\n";return$var;}}$values=array(1,2,3);$it=newMyIterator($values);foreach($itas$a=>$b){print"$a:$b\n";}

天生器

可以说之前为了拥有一个能够被foreach遍历的工具,你不得不去实现一堆的方法,yield关键字便是为了简化这个过程。

天生器供应了一种更随意马虎的方法来实现大略的工具迭代,比较较定义类实现Iterator接口的办法,性能开销和繁芜性大大降落。

<?phpfunctionxrange($start,$end,$step=1){for($i=$start;$i<=$end;$i+=$step){yield$i;}}foreach(xrange(1,1000000)as$num){echo$num,"\n";}

记住,一个函数中如果用了yield,他便是一个天生器,直接调用他是没有用的,不能等同于一个函数那样去实行!

以是,yield便是yield,下次谁再说yield是协程,我肯定把你xxxx。

PHP协程

前面先容协程的时候说了,协程须要程序员自己去编写调度机制,下面我们来看这个机制怎么写。

0)天生器精确利用

既然天生器不能像函数一样直接调用,那么怎么才能调用呢?

方法如下:

foreach他send($value)current / next...

1)Task实现

Task便是一个任务的抽象,刚刚我们说了协程便是用户空间协程,线程可以理解便是跑一个函数。

以是Task的布局函数中便是吸收一个闭包函数,我们命名为coroutine。

/Task任务类/classTask{protected$taskId;protected$coroutine;protected$beforeFirstYield=true;protected$sendValue;/Taskconstructor.@param$taskId@paramGenerator$coroutine/publicfunction__construct($taskId,Generator$coroutine){$this->taskId=$taskId;$this->coroutine=$coroutine;}/获取当前的Task的ID@returnmixed/publicfunctiongetTaskId(){return$this->taskId;}/判断Task实行完毕了没有@returnbool/publicfunctionisFinished(){return!$this->coroutine->valid();}/设置下次要传给协程的值,比如$id=(yield$xxxx),这个值就给了$id了@param$value/publicfunctionsetSendValue($value){$this->sendValue=$value;}/运行任务@returnmixed/publicfunctionrun(){//这里要把稳,天生器的开始会reset,以是第一个值要用current获取if($this->beforeFirstYield){$this->beforeFirstYield=false;return$this->coroutine->current();}else{//我们说过了,用send去调用一个天生器$retval=$this->coroutine->send($this->sendValue);$this->sendValue=null;return$retval;}}}

2)Scheduler实现

接下来便是Scheduler这个重点核心部分,他扮演着调度员的角色。

/ClassScheduler/ClassScheduler{/@varSplQueue/protected$taskQueue;/@varint/protected$tid=0;/Schedulerconstructor./publicfunction__construct(){/事理便是掩护了一个行列步队,前面说过,从编程角度上看,协程的思想实质上便是掌握流的主动让出(yield)和规复(resume)机制/$this->taskQueue=newSplQueue();}/增加一个任务@paramGenerator$task@returnint/publicfunctionaddTask(Generator$task){$tid=$this->tid;$task=newTask($tid,$task);$this->taskQueue->enqueue($task);$this->tid++;return$tid;}/把任务进入行列步队@paramTask$task/publicfunctionschedule(Task$task){$this->taskQueue->enqueue($task);}/运行调度器/publicfunctionrun(){while(!$this->taskQueue->isEmpty()){//任务出队$task=$this->taskQueue->dequeue();$res=$task->run();//运行任务直到yieldif(!$task->isFinished()){$this->schedule($task);//任务如果还没完备实行完毕,入队等下次实行}}}}

这样我们基本就实现了一个协程调度器。

你可以利用下面的代码来测试:

<?phpfunctiontask1(){for($i=1;$i<=10;++$i){echo"Thisistask1iteration$i.\n";yield;//主动让出CPU的实行权}}functiontask2(){for($i=1;$i<=5;++$i){echo"Thisistask2iteration$i.\n";yield;//主动让出CPU的实行权}}$scheduler=newScheduler;//实例化一个调度器$scheduler->newTask(task1());//添加不同的闭包函数作为任务$scheduler->newTask(task2());$scheduler->run();

关键说下在哪里能用得到PHP协程。

functiontask1(){/这里有一个远程任务,须要耗时10s,可能是一个远程机器抓取剖析远程网址的任务,我们只要提交末了去远程机器拿结果就行了/remote_task_commit();//这时候要求发出后,我们不要在这里等,主动让出CPU的实行权给task2运行,他不依赖这个结果yield;yield(remote_task_receive());...}functiontask2(){for($i=1;$i<=5;++$i){echo"Thisistask2iteration$i.\n";yield;//主动让出CPU的实行权}}

这样就提高了程序的实行效率。

关于『系统调用』的实现,鸟哥已经讲得很明白,我这里不再解释。

3)协程堆栈

鸟哥文中还有一个协程堆栈的例子。

我们上面说过了,如果在函数中利用了yield,就不能当做函数利用。

以是你在一个协程函数中嵌套其余一个协程函数:

<?phpfunctionechoTimes($msg,$max){for($i=1;$i<=$max;++$i){echo"$msgiteration$i\n";yield;}}functiontask(){echoTimes('foo',10);//printfootentimesecho"---\n";echoTimes('bar',5);//printbarfivetimesyield;//forceittobeacoroutine}$scheduler=newScheduler;$scheduler->newTask(task());$scheduler->run();

这里的echoTimes是实行不了的!
以是就须要协程堆栈。

不过没紧要,我们改一改我们刚刚的代码。

把Task中的初始化方法改下,由于我们在运行一个Task的时候,我们要剖析出他包含了哪些子协程,然后将子协程用一个堆栈保存。
(C措辞学的好的同学自然能理解这里,不理解的同学我建议去理解下进程的内存模型是怎么处理函数调用)

/Taskconstructor.@param$taskId@paramGenerator$coroutine/publicfunction__construct($taskId,Generator$coroutine){$this->taskId=$taskId;//$this->coroutine=$coroutine;//换成这个,实际Task->run的便是stackedCoroutine这个函数,不是$coroutine保存的闭包函数了$this->coroutine=stackedCoroutine($coroutine);}

当Task->run()的时候,一个循环来剖析:

/@paramGenerator$gen/functionstackedCoroutine(Generator$gen){$stack=newSplStack;//不断遍历这个传进来的天生器for(;;){//$gen可以理解为指向当前运行的协程闭包函数(天生器)$value=$gen->current();//获取中断点,也便是yield出来的值if($valueinstanceofGenerator){//如果是也是一个天生器,这便是子协程了,把当前运行的协程入栈保存$stack->push($gen);$gen=$value;//把子协程函数给gen,连续实行,把稳接下来便是实行子协程的流程了continue;}//我们对子协程返回的结果做了封装,下面讲$isReturnValue=$valueinstanceofCoroutineReturnValue;//子协程返回`$value`须要主协程帮忙处理if(!$gen->valid()||$isReturnValue){if($stack->isEmpty()){return;}//如果是gen已经实行完毕,或者碰着子协程须要返回值给主协程去处理$gen=$stack->pop();//出栈,得到之前入栈保存的主协程$gen->send($isReturnValue?$value->getValue():NULL);//调用主协程处理子协程的输出值continue;}$gen->send(yield$gen->key()=>$value);//连续实行子协程}}

然后我们增加echoTime的结束标示:

classCoroutineReturnValue{protected$value;publicfunction__construct($value){$this->value=$value;}//获取能把子协程的输出值给主协程,作为主协程的send参数publicfunctiongetValue(){return$this->value;}}functionretval($value){returnnewCoroutineReturnValue($value);}

然后修正echoTimes:

functionechoTimes($msg,$max){for($i=1;$i<=$max;++$i){echo"$msgiteration$i\n";yield;}yieldretval("");//增加这个作为结束标示}

Task变为:

functiontask1(){yieldechoTimes('bar',5);}

这样就实现了一个协程堆栈,现在你可以举一反三了。

4)PHP7中yield from关键字

PHP7中增加了yield from,以是我们不须要自己实现携程堆栈,真实太好了。

把Task的布局函数改回去:

publicfunction__construct($taskId,Generator$coroutine){$this->taskId=$taskId;$this->coroutine=$coroutine;//$this->coroutine=stackedCoroutine($coroutine);//不须要自己实现了,改回之前的}

echoTimes函数:

functionechoTimes($msg,$max){for($i=1;$i<=$max;++$i){echo"$msgiteration$i\n";yield;}}

task1天生器:

functiontask1(){yieldfromechoTimes('bar',5);}

functiontask1(){yieldfromechoTimes('bar',5);}

这样,轻松调用子协程。

相关文章

R语言在非统计领域的应用与价值

随着信息技术的飞速发展,数据分析已成为各个行业的重要手段。R语言作为一种开源、功能强大的统计软件,在统计学领域得到了广泛应用。R语...

SEO优化 2024-12-27 阅读0 评论0

R语言库安装指南,开启数据分析之旅

R语言作为一款功能强大的统计软件,在数据分析、统计建模、图形可视化等方面有着广泛的应用。随着R语言的不断发展,越来越多的库被开发出...

SEO优化 2024-12-27 阅读0 评论0

R语言在正实数领域的应用与发展

R语言作为一种开源的统计计算软件,具有强大的数据处理和分析能力。在正实数领域,R语言的应用日益广泛,本文将从正实数的概念、R语言在...

SEO优化 2024-12-27 阅读0 评论0

R语言在数据分析中的应用与探索

随着大数据时代的到来,数据分析已成为各行各业的重要竞争力。R语言作为一种功能强大的统计编程语言,在数据分析领域具有广泛的应用。本文...

SEO优化 2024-12-27 阅读0 评论0