PHP协程入门详解
概念
咱们知道多进程和多线程是实现并发的有效方式。但多进程的上下文切换资源开销太大;多线程开销相比要小很多,也是现在主流的做法,但其的控制权在内核,从而使用户(程序员)失去了对代码的控制,而且线程的上下文切换也是有一定开销的。 这时为了解决以上问题,"协程"(coroutine)的概念就产生了。你可以将协程理解为更轻量级的线程。这种线程叫做“用户空间线程“。协程,有下面两个特点:
- 协同。因为是由程序员自己写的调度策略,其通过协作而不是抢占来进行切换
- 在用户态完成创建,切换和销毁
PHP对协程的支持是在迭代生成器的基础上, 增加了可以回送数据给生成器的功能(调用者发送数据给被调用的生成器函数)。 这就把生成器到调用者的单向通信转变为两者之间的双向通信。
迭代器
迭代器的概念这里就不赘述了。下面看看我们自己实现的一个迭代器。
class MyIterator implements Iterator
{
private $var = array(); public function __construct($array)
{
if (is_array($array)) {
$this->var = $array;
}
} public function rewind() { // 第一次迭代时候会执行(或调用该方法的时候),后面的迭代将不会执行。
echo "rewinding\n";
reset($this->var);
} public function current() {
$var = current($this->var);
echo "current: $var\n";
return $var;
} public function key() {
$var = key($this->var);
echo "key: $var\n";
return $var;
} public function next() { // 最后执行,就是执行完下面sleep(2)后再执行。(执行了next本次迭代才算结束)
$var = next($this->var);
echo "next: $var\n";
return $var;
} public function valid() { // 当valid返回false的时候迭代结束
$var = $this->current() !== false;
echo "valid: {$var}\n";
return $var;
}
} $values = array(1,2,3,4);
$it = new MyIterator($values); foreach ($it as $a => $b) { // 进行迭代(每次迭代,会依次执行以下方法: rewind(特别之处见上面解释), valid, current, key, next)
print "=====\n";
sleep(2);
}
输出:
rewinding
current: 1 // 因为valid里面调用了current, 这里current出来一次
valid: 1
current: 1
key: 0
=====
next: 2
current: 2
valid: 1
current: 2
key: 1
=====
next: 3
current: 3
valid: 1
current: 3
key: 2
=====
next: 4
current: 4
valid: 1
current: 4
key: 3
=====
next:
current:
valid: // valid返回false,迭代结束
生成器
有了yeild的方法就是一个生成器(生成器实现了Iterator接口,即一个生成器有迭代器的特点)。生成器的实现如下:
function xrange($start, $end, $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
echo $i . "\n";
yield;
}
}
// foreach方式
foreach (xrange(1, 10) as $num) {
}
$gene = xrange(1, 10); // gene就是一个生成器对象
// current
$gene->current(); // 打印1
// next
$gene->next();
$gene->current() // 打印2
输出:
1
2
3
4
5
6
7
8
9
10
1
2
生成器各方法详解可看文档: http://php.net/manual/zh/class.generator.php
注意:
生成器不能像函数一样直接调用,调用方法如下:
1. foreach他
2. send($value)
3. current / next...
yield
yield的语法很灵活,我们用下面的例子,让大家能明白yield语法的使用。
用例1: 让出cpu执行权
function task1 () {
for ($i = 1; $i <= 10; ++$i) {
echo "This is task 1 iteration $i.\n";
yield;// 遇到yield就会主动让出CPU的执行权;
}
}
$a = task1();
$a->current(); // 执行第一次迭代
$a->send(1); // 唤醒当时让出CPU执行权的yield
输出:
This is task 1 iteration 1.
This is task 1 iteration 2.
用例2: yield的返回
// yield返回
function task2 () {
for ($i = 1; $i <= 10; ++$i) {
echo "This is task 2 iteration $i.\n";
yield "lm$i"; // 遇到yield就会主动让出CPU的执行权,for暂停执行, 然后返回"lm"。放在yield后面的值就是返回值
}
} $a = task2();
$res = $a->current(); // 第一次迭代, 遇到yield返回
var_dump($res);
$res = $a->send(1); // 唤醒yield, for继续执行,遇到yield返回。
var_dump($res);
输出:
This is task 2 iteration 1.
string(3) "lm1"
This is task 2 iteration 2.
string(3) "lm2"
用例3: yield接收值
function task3 () {
for ($i = 1; $i <= 10; ++$i) {
echo "This is task 3 iteration $i.\n";
$getValue = yield;// 遇到yield就会主动让出CPU的执行权;send后,将send值赋值给getValue
echo $getValue . " ";
}
}
$a = task3();
$a->current();
$a->send("aa"); // 唤醒yield,并将"aa"值赋值给$getValue变量
输出:
This is task 3 iteration 1.
aa This is task 3 iteration 2.
用例4: yeild接收和返回写在一起
function task4 () {
for ($i = 1; $i <= 10; ++$i) {
echo "This is task 4 iteration $i.\n";
$ret = yield "lm$i"; // yield, 然后返回lm$i; 当send时,将send过来的值赋值给$ret;
echo $ret;
}
}
$a = task4();
var_dump($a->current()); // 返回lm1
var_dump($a->send("hhh ")); // 先唤醒yield, 将"hhh "赋值给$ret,再返回lm2
var_dump($a->send("www ")); // 先唤醒yield, 将"www "赋值给$ret,再返回lm3
输出:
This is task 4 iteration 1.
string(3) "lm1"
hhh This is task 4 iteration 2.
string(3) "lm2"
www This is task 4 iteration 3.
string(3) "lm3"
结语:
如果你有看过鸟哥的这篇文章http://www.laruence.com/2015/05/28/3038.html,应该对协程有个深刻的认识。但里面内容更适合中高级PHP工程师看,而且还得具备一定的操作系统的知识,所以我在此基础上用更通俗的方式,阐明一下PHP的协程概念。协程很强大的功能但相对比较复杂, 也比较难被理解。个人目前还没有遇到合适的场景来使用PHP协程,不过我猜测,由于可以在用户层面实现多并发,所以多用于CLI模式下的web服务开发,比如Golang的goroutine并不是线程,而是协程。还有yield有双向通信的功能,所以还可以实现异步服务,但需要自己写调度器,比如鸟哥这篇博客里面的非阻塞IOweb服务器就是靠协程实现异步了实现的。
以上内容如果有错误还请留言交流。
PHP协程入门详解的更多相关文章
- Lua的协程和协程库详解
我们首先介绍一下什么是协程.然后详细介绍一下coroutine库,然后介绍一下协程的简单用法,最后介绍一下协程的复杂用法. 一.协程是什么? (1)线程 首先复习一下多线程.我们都知道线程——Thre ...
- Python中Paramiko协程方式详解
什么是协程 协程我们可以看做是一种用户空间的线程. 操作系统对齐存在一无所知,需要用户自己去调度. 比如说进程,线程操作系统都是知道它们存在的.协程的话是用户空间的线程,操作系统是不知道的. 为什么要 ...
- Android中的Coroutine协程原理详解
前言 协程是一个并发方案.也是一种思想. 传统意义上的协程是单线程的,面对io密集型任务他的内存消耗更少,进而效率高.但是面对计算密集型的任务不如多线程并行运算效率高. 不同的语言对于协程都有不同的实 ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- SQL注入攻防入门详解
=============安全性篇目录============== 本文转载 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机 ...
- SQL注入攻防入门详解(2)
SQL注入攻防入门详解 =============安全性篇目录============== 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱 ...
- Quartz 入门详解
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个, ...
- Redis快速入门详解
Redis入门详解 Redis简介 Redis安装 Redis配置 Redis数据类型 Redis功能 持久化 主从复制 事务支持 发布订阅 管道 虚拟内存 Redis性能 Redis部署 Redis ...
- [转]SQL注入攻防入门详解
原文地址:http://www.cnblogs.com/heyuquan/archive/2012/10/31/2748577.html =============安全性篇目录============ ...
随机推荐
- angular + socket.io+nodejs
一.服务器端: 基本和nodejs工程相同 https://www.cnblogs.com/xuanmanstein/p/10509445.html 安装socket.io npm i --save ...
- wine安装稳定使用falsh播放器
1安装wine,wine安装使用网上自行查找 2.安装flash播放器.exe 下载附件的falsh播放相关.tar.gz,解压后得到 Flash.ocx (flash10 for windows的插 ...
- navicat premium 破解版
下载链接:https://pan.baidu.com/s/1oNwtr2hdUN9F452xkji0aQ
- 获取的输入内容,没有被P标签包裹的文本和元素进行处理
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- __FILE__、__DIR__区别
__FILE__=D:\Soft\xampp\htdocs\test\test.php __DIR__=D:\Soft\xampp\htdocs\test getcwd()=D:\Soft\xampp ...
- 微信小程序 页面跳转方式
// 保留当前页面,跳转到应用内的某个页面,使用wx.navigateBack可以返回到原页面. // 注意:调用 navigateTo 跳转时,调用该方法的页面会被加入堆栈,但是 redirectT ...
- cmd命令往MySQL数据库提交数据
第一步:MySQL -V检查下载成功否第二步:mysql -u root -p 登陆密码第三步:创建一个数据库 create database if not exists 数据库name: 第四步:展 ...
- LeGO-LOAM编译、运行
LeGO-LOAM Github 地址:https://github.com/RobustFieldAutonomyLab/LeGO-LOAM 相关依赖安装 1. ROS 2.GTSAM 下载gtsa ...
- 【PAT】反转链表
PAT 乙级 1025 给定一个常数 K 以及一个单链表 L,请编写程序将 L 中每 K 个结点反转.例如:给定 L 为 1 → 2 → 3 → 4 → 5 → 6,K 为 3,则输出应该为 3 → ...
- vue-实例生命周期钩子(不太明白)
每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的: var vm = new Vue({ // 选项}) 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要 ...