关于PHP中协程和阻塞的一些理解与思考


Posted in PHP onAugust 11, 2017

前言

本文主要给大家介绍了关于PHP中协程和阻塞的理解与思考,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍:

进程、线程、协程

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

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

PHP中的协程实现基础 yield

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

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

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

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

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

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

class timer {
 private $start = 0; // 定时开始时间
 private $timer; // 间隔的时间差,单位秒
 private $value = 0; // 产生的结果值
 private $callback; // 异步回调
 private $isEnd = false; // 当前定时器任务是否结束
 public function __construct($timer,callable $callback)
 {
 $this->start = time();
 $this->timer = $timer;
 $this->callback = $callback;
 }
 public function run() {
 if($this->valid()) {
 $callback = $this->callback;
 $callback($this->value ++,$this);
 $this->start = time();
 }
 }
 /**
 * 定时执行检查
 */
 public function valid() {
 $end = time();
 if($end - $this->start >= $this->timer) {
 return true;
 } else {
 return false;
 }
 }
 public function setEnd($isEnd) {
 $this->isEnd = $isEnd;
 }
 public function getEnd() {
 return $this->isEnd;
 }
}

/**
 * 模拟阻塞的协程1
 *
 */
function taskObject1() {
 $timer = new timer(1,function($value,timer $timer) {
 if($value >= 5) {
 $timer->setEnd(true);
 }
 echo '<br>'.'A '.$value;
 });
 $tid = (yield getTaskId());
 while (true) {
 if($timer->getEnd() == true) {
 break;
 }
 yield $timer->run();
 }
}
/**
 * 模拟阻塞的协程2
 *
 */
function taskObject2() {
 $timer = new timer(2,function($value,timer $timer) {
 if($value >= 3) {
 $timer->setEnd(true);
 }
 echo '<br>'.'B '.$value;
 });
 $tid = (yield getTaskId());
 while (true) {
 if($timer->getEnd() == true) {
 break;
 }
 yield $timer->run();
 }
}
$scheduler = new Scheduler;
$scheduler->newTask(taskObject1());
$scheduler->newTask(taskObject2());
$scheduler->run();

以上实现的是:

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

思考:

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

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

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

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

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

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

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

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

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
php 全文搜索和替换的实现代码
Jul 29 PHP
php定时删除文件夹下文件(清理缓存文件)
Jan 23 PHP
浅析SVN常见问题及解决方法
Jun 21 PHP
php设置允许大文件上传示例代码
Mar 10 PHP
PHP面向对象继承用法详解(优化与减少代码重复)
Dec 02 PHP
Yii2下点击验证码的切换实例代码
Mar 14 PHP
详解cookie验证的php应用的一种SSO解决办法
Oct 20 PHP
PHP封装的分页类与简单用法示例
Feb 25 PHP
PHP5中使用mysqli的prepare操作数据库的介绍
Mar 18 PHP
PHP实现的62进制转10进制,10进制转62进制函数示例
Jun 06 PHP
laravel unique验证、确认密码confirmed验证以及密码修改验证的方法
Oct 16 PHP
一次项目中Thinkphp绕过禁用函数的实战记录
Nov 17 PHP
如何利用预加载优化Laravel Model查询详解
Aug 11 #PHP
PHP实现的自定义图像居中裁剪函数示例【测试可用】
Aug 11 #PHP
Redis在Laravel项目中的应用实例详解
Aug 11 #PHP
PHP验证码无法显示的原因及解决办法
Aug 11 #PHP
php readfile()修改文件上传大小设置
Aug 11 #PHP
浅谈Laravel中的一个后期静态绑定
Aug 11 #PHP
浅谈PHP中new self()和new static()的区别
Aug 11 #PHP
You might like
复杂检索数据并分页显示的处理方法
2006/10/09 PHP
探讨:使用XMLSerialize 序列化与反序列化
2013/06/08 PHP
ThinkPHP的RBAC(基于角色权限控制)深入解析
2013/06/17 PHP
php实现购物车功能(以大苹果购物网为例)
2017/03/09 PHP
实现复选框全选/全不选切换
2006/12/23 Javascript
直接生成打开窗口代码,不必下载
2008/05/14 Javascript
使用jQuery.Validate进行客户端验证(初级篇) 不使用微软验证控件的理由
2010/06/28 Javascript
JS字符串的切分用法实例
2016/02/22 Javascript
JavaScript html5 canvas绘制时钟效果
2016/03/01 Javascript
浅谈js原生拖放
2016/11/21 Javascript
bootstrap table分页模板和获取表中的ID方法
2017/01/10 Javascript
JavaScript实现图片切换效果
2017/08/12 Javascript
ES6 javascript中class静态方法、属性与实例属性用法示例
2017/10/30 Javascript
微信小程序模版渲染详解
2018/01/26 Javascript
详解vscode中vue代码颜色插件
2018/10/11 Javascript
Jquery异步上传文件代码实例
2019/11/13 jQuery
JavaScript对象属性操作实例解析
2020/02/04 Javascript
[05:10]2014DOTA2国际邀请赛 通往胜利之匙赛场探秘之旅
2014/07/18 DOTA
Python面向对象编程中的类和对象学习教程
2015/03/30 Python
无法使用pip命令安装python第三方库的原因及解决方法
2018/06/12 Python
Python实现的多叉树寻找最短路径算法示例
2018/07/30 Python
使用Python中的reduce()函数求积的实例
2019/06/28 Python
python 调用pyautogui 实时获取鼠标的位置、移动鼠标的方法
2019/08/27 Python
Python Collatz序列实现过程解析
2019/10/12 Python
500行代码使用python写个微信小游戏飞机大战游戏
2019/10/16 Python
django 利用Q对象与F对象进行查询的实现
2020/05/15 Python
Python可以用来做什么
2020/11/23 Python
python 获取计算机的网卡信息
2021/02/18 Python
乌克兰品牌化妆品和香水在线商店:Bomond
2020/01/14 全球购物
交通事故和解协议书
2014/09/25 职场文书
学校班子个人对照检查材料思想汇报
2014/09/27 职场文书
小学教师个人总结
2015/02/05 职场文书
《时代广场的蟋蟀》读后感:真挚友情,温暖世界!
2020/01/08 职场文书
golang 实现两个结构体复制字段
2021/04/28 Golang
深入理解margin塌陷和margin合并的解决方案
2021/06/26 HTML / CSS
使用CSS实现按钮边缘跑马灯动画
2023/05/07 HTML / CSS