关于PHP协程与阻塞的思考

进程、线程、协程

关于进程、线程、协程,有非常详细和丰富的博客或者学习资源,我不在此做赘述,我大致在此介绍一下这几个东西。

  1. 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
  2. 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
  3. 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

PHP中的协程实现基础 yield

yield的根本实现是生成器类,而迭代器类是迭代器接口的实现:


  1. Generator implements Iterator { 
  2.     public mixed current ( void ) // 返回当前产生的值 
  3.     public mixed key ( void ) // 返回当前产生的键 
  4.     public void next ( void ) // 生成器继续执行 
  5.     public void rewind ( void ) // 重置迭代器,如果迭代已经开始了,这里会抛出一个异常。 
  6.                                              // renwind的执行将会导致第一个yield被执行, 并且忽略了他的返回值. 
  7.     public mixed send ( mixed $value ) // 向生成器中传入一个值,并且当做 yield 表达式的结果,然后继续执行生成器。如果当这个方法被调用时,生成器    
  8.                                             // 不在 yield 表达式,那么在传入值之前,它会先运行到第一个 yield 表达式。 
  9.     public void throw ( Exception $exception ) // 向生成器中抛入一个异常 
  10.     public bool valid ( void ) // 检查迭代器是否被关闭 
  11.     public void __wakeup ( void ) // 序列化回调,抛出一个异常以表示生成器不能被序列化。 

以上解析可以参考PHP官方文档。

http://php.net/manual/zh/clas...

以及鸟哥翻译的这篇详细文档:

http://www.laruence.com/2015/...

我就以他实现的协程多任务调度为基础做一下例子说明并说一下关于我在阻塞方面所做的一些思考。

自定义简单定时执行任务示例:

(此例子必须依赖于以上鸟哥实现的协程调度代码)


  1. class timer { 
  2.     private $start = 0; // 定时开始时间 
  3.     private $timer; // 间隔的时间差,单位秒 
  4.     private $value = 0; // 产生的结果值 
  5.     private $callback; // 异步回调 
  6.     private $isEnd = false; // 当前定时器任务是否结束 
  7.     public function __construct($timer,callable $callback) 
  8.     { 
  9.         $this->start = time(); 
  10.         $this->timer = $timer; 
  11.         $this->callback = $callback; 
  12.     } 
  13.     public function run() { 
  14.         if($this->valid()) { 
  15.             $callback = $this->callback; 
  16.             $callback($this->value ++,$this); 
  17.             $this->start = time(); 
  18.         } 
  19.     } 
  20.     /** 
  21.      * 定时执行检查 
  22.      */ 
  23.     public function valid() { 
  24.         $end = time(); 
  25.         if($end - $this->start >= $this->timer) { 
  26.             return true; 
  27.         } else { 
  28.             return false; 
  29.         } 
  30.     } 
  31.     public function setEnd($isEnd) { 
  32.         $this->isEnd = $isEnd; 
  33.     } 
  34.     public function getEnd() { 
  35.         return $this->isEnd; 
  36.     } 
  37.  
  38. /** 
  39.  * 模拟阻塞的协程1 
  40.  * 
  41.  */ 
  42. function taskObject1() { 
  43.     $timer = new timer(1,function($value,timer $timer) { 
  44.         if($value >= 5) { 
  45.             $timer->setEnd(true); 
  46.         } 
  47.         echo '<br>'.'A '.$value; 
  48.     }); 
  49.     $tid = (yield getTaskId()); 
  50.     while (true) { 
  51.         if($timer->getEnd() == true) { 
  52.             break; 
  53.         } 
  54.         yield $timer->run(); 
  55.     } 
  56. /** 
  57.  * 模拟阻塞的协程2 
  58.  * 
  59.  */ 
  60. function taskObject2() { 
  61.     $timer = new timer(2,function($value,timer $timer) { 
  62.         if($value >= 3) { 
  63.             $timer->setEnd(true); 
  64.         } 
  65.         echo '<br>'.'B '.$value; 
  66.     }); 
  67.     $tid = (yield getTaskId()); 
  68.     while (true) { 
  69.         if($timer->getEnd() == true) { 
  70.             break; 
  71.         } 
  72.         yield $timer->run(); 
  73.     } 
  74. $scheduler = new Scheduler; 
  75. $scheduler->newTask(taskObject1()); 
  76. $scheduler->newTask(taskObject2()); 
  77. $scheduler->run(); 

以上实现的是:

  1. 产生两个任务,并行执行,并且给每个任务在执行的时候模拟几秒钟的阻塞;
  2. 让协程切换的时候能顺利切换,其中的任务阻塞不相互影响;

思考:

我为什么要做以上这件事情呢?因为我发现协程实现虽然很强大也很有意思,能让多任务并行,但是我在其中一个任务里调用系统函数 sleep() 的时候,阻塞任务会阻止协程切换,其实从协程的实现原理上来书也是这么回事。

那么,我也就想模拟协程阻塞,但是不产生阻塞看是否可行。PHP本身只提供了生成器为协程调用提供了支撑,如果不依赖扩展,没有提供多线程的程序实现方式,没有java那么强大,可以开子线程进行实现。

我印象中java的子线程是独立执行且不会相互阻塞的,所以我在想,PHP既然可以实现类似于多线程这样的机制,那么能不能实现调用过程中非阻塞呢?

经过这样一个实现和思考,一开始是陷入了一个误区的,是由于PHP原生函数 sleep() 阻塞造成的思维误区,那就是认为要想真正实现非阻塞或者说实现异步的话,是必须依赖于语言底层的。

后来,我想明白了一个道理,既然某个方法或者函数在执行过程中,会产生阻塞,那么把当前这个方法换成自定义的,做成非阻塞(相对于整个协程调度来说)不就行了吗?比如上面的定时执行我自己实现了一个。

而另一方面,协程调度本身的目的也是为了把任务执行过程切成尽量小片,从而快速切换执行,达到并行的目的。从这方面来看,协程应该也算是一种程序设计思想。

以下是一个程序切成尽量小片执行的例子:


  1. // 一个简单的例子 
  2. <?php 
  3. function xrange($start, $end, $step = 1) { 
  4.     for ($i = $start; $i <= $end; $i += $step) { 
  5.         yield $i; 
  6.     } 
  7.   
  8. foreach (xrange(1, 1000000) as $num) { 
  9.     echo $num, "\n"; 

这个例子是把原本用 range 生成一个很大的整型数组的方式切换为分片执行,也就是说在遍历的时候再去取到指定的值,从代码上来看,内存消耗相对于之前来说就非常小了。

作者:kumfo

来源:51CTO

时间: 2024-09-08 01:16:10

关于PHP协程与阻塞的思考的相关文章

PHP协程实现过程详解

多进程/线程 最早的服务器端程序都是通过多进程.多线程来解决并发IO的问题.进程模型出现的最早,从Unix 系统诞生就开始有了进程的概念.最早的服务器端程序一般都是 Accept 一个客户端连接就创建一个进程,然后子进程进入循环同步阻塞地与客户端连接进行交互,收发处理数据. 多线程模式出现要晚一些,线程与进程相比更轻量,而且线程之间共享内存堆栈,所以不同的线程之间交互非常容易实现.比如实现一个聊天室,客户端连接之间可以交互,聊天室中的玩家可以任意的其他人发消息.用多线程模式实现非常简单,线程中可

介绍Python的Tornado框架中的协程异步实现原理

  介绍Python的Tornado框架中的协程异步实现原理        这篇文章主要介绍了简单介绍Python的Tornado框架中的协程异步实现原理,作者基于Python的生成器讲述了Tornado异步的特点,需要的朋友可以参考下 Tornado 4.0 已经发布了很长一段时间了, 新版本广泛的应用了协程(Future)特性. 我们目前已经将 Tornado 升级到最新版本, 而且也大量的使用协程特性. 很长时间没有更新博客, 今天就简单介绍下 Tornado 协程实现原理, Tornad

谈谈Python协程技术的演进

一.引言 1. 存储器山 存储器山是 Randal Bryant 在<深入理解计算机系统>一书中提出的概念. 基于成本.效率的考量,计算机存储器被设计成多级金字塔结构,塔顶是速度最快.成本最高的 CPU 内部的寄存器(一般几 KB)与高速缓存,塔底是成本最低.速度最慢的广域网云存储(如百度云免费 2T ) 存储器山的指导意义在于揭示了良好设计程序的必要条件是需要有优秀的局部性: 时间局部性:相同时间内,访问同一地址次数越多,则时间局部性表现越佳; 空间局部性:下一次访问的存储器地址与上一次的访

进程,线程,协程

最早出现的是进程,后来为了调度的方便出现了线程,现在又蹦出了一个协程.这到底是个什么东西呢. 并发和并行: 最早的计算机,每次只能执行一个程序,别的都得等着.到后来,计算机运算速度提高了,于是就想要同一时间执行那么三五个程序,几个程序能一块跑一跑.特别是UI什么的,别跑个程序得排队等着.于是就有了并发. 从程序员的角度可以看成是多个独立的逻辑流.把单cpu时间分片,能快速的切换逻辑流,看起来像是大家一块跑的.这个时候内存其实是共享的.后来一电脑上有了好几个cpu,好咧,大家都别闲着,可以一块跑.

协程 [wiki]

协程 维基百科,自由的百科全书 与子例程一样,协程也是一种程序组件.相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛.协程源自Simula和Modula-2语言,但也有其他语言支持.协程更适合于用来实现彼此熟悉的程序组件,如合作式多任务,迭代器,无限列表和管道. 协程最初在1963年被提出.[1] 简单的对比和示例 由于协程不如子例程那样被普遍所知,最好对它们作个比较. 子例程的起始处是惟一的入口点,一旦退出即完成了子例程的执行,子例程的一个实例只会返回一次. 协程可以通过y

浅谈我对协程的理解

我心中的协程 最近在研究网络服务框架方面的东西,发现了一个神奇的东西-协程. 一句话说明什么是线程:协程是一种用户态的轻量级线程. 一句话并不能完全概括协程的全部,但是起码能让我们对协程这个概念有一个基本的印象. 从硬件发展来看,从最初的单核单CPU,到单核多CPU,多核多CPU,似乎已经到了极限了,但是单核CPU性能却还在不断提升.server端也在不断的发展变化.如果将程序分为IO密集型应用和CPU密集型应用,二者的server的发展如下: IO密集型应用: 多进程->多线程->事件驱动-

ucontext-人人都可以实现的简单协程库

1.干货写在前面 协程是一种用户态的轻量级线程.本篇主要研究协程的C/C++的实现. 首先我们可以看看有哪些语言已经具备协程语义: 比较重量级的有C#.erlang.golang* 轻量级有python.lua.javascript.ruby 还有函数式的scala.scheme等. c/c++不直接支持协程语义,但有不少开源的协程库,如: Protothreads:一个"蝇量级" C 语言协程库 libco:来自腾讯的开源协程库libco介绍,官网 coroutine:云风的一个C语

一个使用 asyncio 协程的网络爬虫(二)

协程 还记得我们对你许下的承诺么?我们可以写出这样的异步代码,它既有回调方式的高效,也有多线程代码的简洁.这个结合是同过一种称为协程coroutine的模式来实现的.使用 Python3.4 标准库 asyncio 和一个叫"aiohttp"的包,在协程中获取一个网页是非常直接的( @asyncio.coroutine 修饰符并非魔法.事实上,如果它修饰的是一个生成器函数,并且没有设置 PYTHONASYNCIODEBUG 环境变量的话,这个修饰符基本上没啥用.它只是为了框架的其它部分

Python协程:概念及其用法

真正有知识的人的成长过程,就像麦穗的成长过程:麦穗空的时候,麦子长得很快,麦穗骄傲地高高昂起,但是,麦穗成熟饱满时,它们开始谦虚,垂下麦芒. --蒙田<蒙田随笔全集> 上篇<Python 多线程鸡年不鸡肋>论述了关于python多线程是否是鸡肋的问题,得到了一些网友的认可,当然也有一些不同意见,表示协程比多线程不知强多少,在协程面前多线程算是鸡肋.好吧,对此我也表示赞同,然而上篇我论述的观点不在于多线程与协程的比较,而是在于IO密集型程序中,多线程尚有用武之地. 对于协程,我表示其