什么是协程
理解协程之前最好要理解进程和线程,这里不过多解释,简单来说,进程是资源分配的最小单位,线程是进程中一个单一的执行流,线程共享进程资源,每个线程都有自己独立的栈空间。线程相对于进程而言更加轻量,操作系统调度进程切换的代价很大,需要保存当前进程的各种信息,PCB 进程控制块。线程切换相对更加容易,线程同属于一个进程,只需要切换栈空间。多线程更能利用多核的 cpu,发挥性能。
协程呢,可以说是断点,就如同程序调试时的断点一样,协程可以打破程序顺序执行的特征,协程可以帮助我们调整代码的执行顺序,来实现任务协同工作。网上有好多协程与线程的对比文章,有的说协程比线程好,有的则说线程好,我也尝试了 php 中的协程,使用 yield 关键字。通过我的尝试以及我的理解,我认为二者各自优点,但是我还是更加倾向于线程。我觉得线程时任务并行成为可能,而协程只是改变了代码的执行顺序,能用协程做到的,我们可以通过调整代码的顺序来实现相同的效果。
还有一点就是,有人说协程可以实现异步,我却没有发现协程这方面的作用,我觉得可以用协程调度程序任务,然后用线程并行来实现异步,但是目前我还不会
php 中协程的使用
php 中的协程就是在函数中使用 yield 关键字,就可以成为一个生成器来实现协程,生成器是 Geneator 的一个实例对象,所以最好先理解一下迭代器提供的几个抽象函数,rewind(), current(), next(), valid()比如下面一个简单的例子
function go(){
yield;
echo 'yield ';
yield;
echo 'end';
};
$test = go();
假如函数中没有那两个 yield 关键字,上述代码会输出 yield end,可是加上 yield,会发现程序什么输出都没有,这是因为 yield 相当于一个断点,函数走到这里会停止,让出 cpu ,这也是协程的工作原理
包含 yield 的函数是生成器函数,不同于普通函数,生成器函数被调用时,实际是返回了一个可迭代的对象,是 Geneator类 的一个实例,迭代对象通过 next() 方法可以遍历到下一个对象,对于生成器,可以使用 next() 实现程序的继续执行。
还是上面的例子,详细解释一下如何工作,加深自己的理解
$yield= function(){
yield;
echo 'yield ';
yield;
echo 'end';
};
$test = $yield();// 生成一个Geneator 的实例对象,对象实例化的时候,会执行一次 rewind() 相当于生成器函数执行到第一次 yield 处中断函数
$test->next();// 输出 yield 调用 next() 就是使程序继续执行,执行到下一个 yield 处再次中断,所以会输入 yield
$test->next();// 输出 end 同上,这个执行后,函数内部不再有中断点
var_dump($test->valid()); // 这里会输出 false 说明迭代结束
下面再介绍一个函数 send(),可以实现我们与生成器的通信。先说一下总结的结论,send() 与 next() 都能让生成器向后迭代一步,在函数中表现出来就是让函数再向下执行,直到遇到下一个 yield。next() 比较直接,就是调度生成器执行至下一个断点处,没有返回值,send() 相对而言增加了通信效果,send() 发送数据至生成器,被 yield 关键字接收,可以赋值给变量,同时执行至下一处断点 yield 处,将 yield 右边的数据作为返回值返回。
相比来说,send()相当于执行 next() 后执行 current();next() 相当于 send(null),而且不取返回值;虽然这样表达不是很准确
还是写几个例子便于理解掌握
// 上面的例子中,只单独使用了 yield 关键字,其实 yield 左右两边都是可以跟表达式和变量的
// 先来一个简单的便于理解
function work(){
$str = yield 'first yield';
echo $str;
yield 'end';
}
$s = work();
var_dump($a->current()); // string(11) "first yield"
var_dump($s->next()); // NULL
// 如果将上面的 var_dump($s->next()) 换成 $s->send('php') 会输出 php 并得到 string(3) "end"
迭代器的 current() 以及 send() 会返回当前 yield 右侧的值,如果右侧没有,则返回空,next() 则是继续上一个断点,继续执行到下一个 yield 或者迭代器结束的地方,比如 return 语句,生成器的迭代是通过 valid() 来判断的,valid() 返回 false 时说明迭代结束
$work = function(){
$i = 0;
while(++$i < 10){
$str = yield $i;
var_dump($str);
}
};
// case 1
$test = $work();
foreach($test as $v){
echo $v,PHP_EOL;
}
// 上面的foreach 实际上就是
foreach($test->rewind(),$v = $test->current();$test->valid();$test->next(),$v = $test->current()){
echo $v,PHP_EOL;
}
// case 2
$i = 0;
$test = $work();
while($test->valid()){
$test->send('send '.$i++);
}
// 上述两段代码输出不同 能看出 next() 就是仅仅向下执行,不传入数据
// send() 会传入数据,在生成器中被 yield 接收,用来传递给左侧变量(没有赋值操作的话的话 send() 也就没有使用的意义)
一个更能看出的例子
$s = (function($num){
while(--$num){
var_dump(yield);
}
})(10);
$i = 0;
while($s->valid()){
//$s->next();
$s->send(++$i);// 试着将这条语句注释,执行上方的语句,看看有什么不同的效果
}
除了可以设置生成器每次迭代的值之外,也可以设置键
function work($i){
while(--$i > 0){
yield $i * $i => $i;
}
}
$test = work(5);
while($test->valid()){
echo $test->key(),'----',$test->current(),PHP_EOL;
$test->next();
}
如果不使用 $key => $value 的格式,右侧的变量只能作为 value,键值默认为数字索引
使用协程写两个例子
生产者与消费者 参考链接
const NUM = 10; // 设置生产个数,作为终止条件
$consumer = (function(){
while(true){
$receive = yield; // 设置断点
echo 'receive '.$receive,PHP_EOL;
echo 'consume end',PHP_EOL;
}
})();
$producer = (function($num) use ($consumer){
echo 'start produce',PHP_EOL,'-----',PHP_EOL;
while(--$num > 0){
echo 'produce num '.$num,PHP_EOL;
$consumer->send('produce num '.$num); // 生产后通知 consumer 消费
}
echo '------',PHP_EOL,'produce end';
})(NUM);
再写一个之前线程实现的 线程同步打印 ABC
/**
* 实现方式,A 打印后通知 B ,B 打印后通知 C,C 打印后返回 A 处,继续执行
* 所以将 B 和 C 用生成器实现,A 调用 B,B 调用 C
*/
const NUM = 10; // 打印次数
const SLEEP_TIME = 1e6; //休眠时间
$c = (function(){
while(true){
yield;
echo 'C';
usleep(SLEEP_TIME);// 为了看出效果,延时一下
}
})();
$b = (function() use ($c){
while(true){
yield;
echo 'B';
usleep(SLEEP_TIME);
$c->next();
}
})();
$a = (function($num) use ($b){
while (--$num > 0) {
echo 'A';
usleep(SLEEP_TIME);
$b->next();
}
echo PHP_EOL,'print end';
})(NUM);
个人见解
通过对协程的使用,我还是认为协程只是调整了代码的执行顺序,(能通过协程实现的,我们可以自己改写代码顺序来实现),并不能实现并行。协程切换的代价小,但是依旧不能取代线程的作用,协程如果能配合线程,来实现异步,将任务分发给线程,通过协程调度任务,应该会是一个不错的方案
标签:协程,send,next,线程,yield,test,php
来源: https://www.cnblogs.com/mlover/p/11134493.html