首先是,这是我第一次把公众号文章复制粘贴到github来。

其次是,很久很久之前,我挖了一个yield的一个坑,自己挖的坑自己填,不然迟早会把自己埋掉。

最后是,如果想看之前那个坑,请发送“yield”给README中的公众号,我开通了高大上的自动回复功能,稀罕地不得了!

PS:那篇文章中在最后我犯了一个错误,误下了一个结论:foreach中不能使用send并猜测这是PHP的bug,实际上并不是,真实的原因粗暴简单的理解就是send会让生成器继续执行一次导致。这件事情告诉我们:

除了装逼之外,甩锅也是有打脸风险的

那篇坑里,内容和你能在百毒上搜索到的大多数文章都是差不多的,不过我那篇坑标题起得好:《yield是个什么玩意(上)》,也就是暗示大家还有下篇,所以起标题也是需要一定技术含量的。

我坚信,在座的各位辣鸡在看完上篇坑文后最想说的注定是泰迪熊这句话(这是文化属性,不以各位的意志而转移):

回到今天主旨上来,强调几点:

  • 虽然文章标题中有“yield和协程”这样的关键字,但实际上yield并不是协程,看起来有不少人直接将yield和协程划了等号。yield的本质是生成器,英文名字叫做Generator。

  • yield只能用在function中,但用了yield就已经不是传统意义上的function了,同时如果你企图在function之外的其他地方用yield,你会被打脸。

  • yield的最重要作用就是:自己中断一坨代码的执行,然后主动让出CPU控制权给路人甲;然后又能通过一些方式从刚才中断的地方恢复运行。这个就比较屌了,假如你请求了一个费时10s的服务器API,此时是可以让出CPU给路人甲。粗暴地说上面的过程就算是协程的基本概念。

多线程和多进程都是操作系统参与的调度,而协程是用户自主实现的调度,协程的关键点实际上是“用户层实现自主调度”,大概有“翻身农奴把歌唱”的意思。

下面我通过一坨代码来体会一把“翻身农奴”,你们感受一下:

  1. <?php
  2. function gen1() {
  3. for( $i = 1; $i <= 10; $i++ ) {
  4. echo "GEN1 : {$i}".PHP_EOL;
  5. // sleep没啥意思,主要就是运行时候给你一种切实的调度感,你懂么
  6. // 就是那种“你看!你看!尼玛,我调度了!卧槽”
  7. sleep( 1 );
  8. // 这句很关键,表示自己主动让出CPU,我不下地狱谁下地狱
  9. yield;
  10. }
  11. }
  12. function gen2() {
  13. for( $i = 1; $i <= 10; $i++ ) {
  14. echo "GEN2 : {$i}".PHP_EOL;
  15. // sleep没啥意思,主要就是运行时候给你一种切实的调度感,你懂么
  16. // 就是那种“你看!你看!尼玛,我调度了!卧槽”
  17. sleep( 1 );
  18. // 这句很关键,表示自己主动让出CPU,我不下地狱谁下地狱
  19. yield;
  20. }
  21. }
  22. $task1 = gen1();
  23. $task2 = gen2();
  24. while( true ) {
  25. // 首先我运行task1,然后task1主动下了地狱
  26. echo $task1->current();
  27. // 这会儿我可以让task2介入进来了
  28. echo $task2->current();
  29. // task1恢复中断
  30. $task1->next();
  31. // task2恢复中断
  32. $task2->next();
  33. }

上面代码执行结果如下图:

虽然我话都说到这里了,但是肯定还是有人get不到“所以,到底发生了什么?”。你要知道,如果function gen1和function gen2中没有yield,而是普通函数,你是无法中断其中的for循环的,诸如下面这样的代码:

  1. <?php
  2. function gen1() {
  3. for( $i = 1; $i <= 10; $i++ ) {
  4. echo "GEN1 : {$i}".PHP_EOL;
  5. sleep( 1 );
  6. }
  7. }
  8. function gen2() {
  9. for( $i = 1; $i <= 10; $i++ ) {
  10. echo "GEN2 : {$i}".PHP_EOL;
  11. }
  12. }
  13. gen1();
  14. gen2();
  15. // 看这里,看这里,看这里!
  16. // 上面的代码一旦运行,一定是先运行完gen1函数中的for循环
  17. // 其次才能运行完gen2函数中的for循环,绝对不会出现
  18. // gen1和gen2交叉运行这种情况

我似乎已然精通了yield

写到这里后我也开始蹩了,和以往的憋了三天蹦不出来个屁有所不同,我这次蹩出了一个比较典型的应用场景:curl。下面我们基于上面那坨辣鸡代码将gen1修改为一个耗时curl网络请求,gen2将向一个文本文件中写内容,我们的目的就是在耗时的curl开始后主动让出CPU,让gen2去写文件,以实现CPU的最大化利用。

  1. <?php
  2. $ch1 = curl_init();
  3. // 这个地址中的php,我故意sleep了5秒钟,然后输出一坨json
  4. curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" );
  5. curl_setopt( $ch1, CURLOPT_HEADER, 0 );
  6. $mh = curl_multi_init();
  7. curl_multi_add_handle( $mh, $ch1 );
  8. function gen1( $mh, $ch1 ) {
  9. do {
  10. $mrc = curl_multi_exec( $mh, $running );
  11. // 请求发出后,让出cpu
  12. yield;
  13. } while( $running > 0 );
  14. $ret = curl_multi_getcontent( $ch1 );
  15. echo $ret.PHP_EOL;
  16. return false;
  17. }
  18. function gen2() {
  19. for ( $i = 1; $i <= 10; $i++ ) {
  20. echo "gen2 : {$i}".PHP_EOL;
  21. file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND );
  22. yield;
  23. }
  24. }
  25. $gen1 = gen1( $mh, $ch1 );
  26. $gen2 = gen2();
  27. while( true ) {
  28. echo $gen1->current();
  29. echo $gen2->current();
  30. $gen1->next();
  31. $gen2->next();
  32. }

上面的代码,运行以后,我们再等待curl发起请求的5秒钟内,同时可以完成文件写入功能,如果换做平时的PHP程序,就只能是先阻塞等待curl拿到结果后才能完成文件写入。

文章太长,就像“老太太的裹脚布一样,又臭又长”,所以,最后再对代码做个极小幅度的改动就收尾不写了!

  1. <?php
  2. $ch1 = curl_init();
  3. // 这个地址中的php,我故意sleep了5秒钟,然后输出一坨json
  4. curl_setopt( $ch1, CURLOPT_URL, "http://www.selfctrler.com/index.php/test/test1" );
  5. curl_setopt( $ch1, CURLOPT_HEADER, 0 );
  6. $mh = curl_multi_init();
  7. curl_multi_add_handle( $mh, $ch1 );
  8. function gen1( $mh, $ch1 ) {
  9. do {
  10. $mrc = curl_multi_exec( $mh, $running );
  11. // 请求发出后,让出cpu
  12. $rs = yield;
  13. echo "外部发送数据{$rs}".PHP_EOL;
  14. } while( $running > 0 );
  15. $ret = curl_multi_getcontent( $ch1 );
  16. echo $ret.PHP_EOL;
  17. return false;
  18. }
  19. function gen2() {
  20. for ( $i = 1; $i <= 10; $i++ ) {
  21. echo "gen2 : {$i}".PHP_EOL;
  22. file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND );
  23. $rs = yield;
  24. echo "外部发送数据{$rs}".PHP_EOL;
  25. }
  26. }
  27. $gen1 = gen1( $mh, $ch1 );
  28. $gen2 = gen2();
  29. while( true ) {
  30. echo $gen1->current();
  31. echo $gen2->current();
  32. $gen1->send("gen1");
  33. $gen2->send("gen2");
  34. }

我们修改了内容:

将$gen1->next()修改成了$gen1->send(“gen1”)

在function gen1中yield有了返回值,并且将返回值打印出来

这件事情告诉我们:yield和send,是可以双向通信的,同时告诉我们send可以用来恢复原来中断的代码,而且在恢复中断的同时可以携带信息回去。写到这里,你是不是觉得这玩意的可利用价值是不是比原来高点儿了?

我知道,有人肯定叨叨了:“老李,你代码特么写的真是辣鸡啊!你之前保证过了的 —- 只在公司生产环境写辣鸡代码的。可你看看你这辣鸡光环到笼罩都到demo里了,你连demo都不放过了!你怎么说?!”。兄dei,“又不是不能用”。而且我告诉你,上面这点儿curl demo来讲明白yield还是不够的,后面还有两三篇yield呢,照样是烂代码恶心死你,爱看不看。我劝你心放宽,你想想你这么烂的代码都经历了,还有什么不能经历的?

文章最后补个小故事:其实yield是PHP 5.5就已经添加进来了,这个模块的作者叫做Nikita Popov,网络上的名称是Nikic。我们知道PHP7这一代主力是惠新宸,下一代PHP主力就是Nikic了。早在2012年,Nikic就发表了一篇关于PHP yield多任务的文章,链接我贴出来大家共赏一下 —- http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html

上一篇:17. PHP中的yield

  • 0. 介绍
  • 1. php进程daemon化的正确做法
  • 2. php多进程初探—-开篇
  • 3. php多进程初探—-孤儿和僵尸
  • 4. php多进程初探—-信号
  • 5. swoole的进程模型
  • 6. php多进程初探—-利用多进程开发点儿东西
  • 7. php多进程初探—-再次谈daemon进程
  • 8. php多进程初探—-进程间通信二三事
  • 9. PHP Socket初探—-先从一个简单的socket服务器开始
  • 10. PHP socket初探—-关于IO的一些枯燥理论
  • 11. PHP socket初探—-select系统调用
  • 12. PHP socket初探 —- 颤颤抖抖开篇epoll(一)
  • 13. PHP socket初探 —- 硬着头皮继续libevent(二)
  • 14. PHP socket初探 —- 含着泪也要磕完libevent(三)
  • 15. PHP socket初探 —- 一些零碎细节的拾漏补缺(一)
  • 16. swoole的协程是个什么鬼
  • 17. PHP中的yield
  • 18.填坑之PHP的yield和协程在一起的日子里

转载地址:https://www.bookstack.cn/read/advanced-php/18.%E5%A1%AB%E5%9D%91%E4%B9%8BPHP%E7%9A%84yield%E5%92%8C%E5%8D%8F%E7%A8%8B%E5%9C%A8%E4%B8%80%E8%B5%B7%E7%9A%84%E6%97%A5%E5%AD%90%E9%87%8C.md

填坑之PHP的yield和协程在一起的日子里相关推荐

  1. c++ 协程_用yield实现协程

    上一篇 理解python中的yield关键字 介绍了使用yeld实现生成器函数,这一篇我们来继续深入的了解一下yield,用yield实现协程. 先来解答一下上一篇留下的问题:下面的代码为什么第二次调 ...

  2. 发现一个小坑的地方,unity的协程,想要停止,必须以字符串启动

    今天想要停止一个协成,发现调用 StopCoroutine(ShowDebug()); 竟然不管用,后来看了文档才知道,原来想要停止协成,必须用字符启动协程 StartCoroutine(" ...

  3. php curl header_PHP中的yield与协程(二十一节)

    大家好,我是老李. 顺风说骚话,逆风讲道理 最近在大家一起努力下,那个沙雕肺炎患病人数增长率下降了不少,总体来说还算顺,所以今天这篇注定又要骚话连篇了.听说最近不少玉米开始向大连.威海.烟台方向涌入, ...

  4. 学习笔记(35):Python网络编程并发编程-协程(yield,greenlet,gevent模块)

    立即学习:https://edu.csdn.net/course/play/24458/296457?utm_source=blogtoedu 协程(yield,greenlet,gevent) 1. ...

  5. 20170702-变量说明,静态方法,类方法区别,断点调试,fork,yield协程,进程,动态添加属性等。。...

    概念: 并行:同时运行 并发:看似同时运行  json后任然中文的问题 import jsond = {"名字":"初恋这件小事"}new_d1 = json. ...

  6. 理解Python的协程机制-Yield

    根据PEP-0342 Coroutines via Enhanced Generators,原来仅仅用于生成器的yield关键字被扩展,成为Python协程实现的一部分.而之所以使用协程,主要是出于性 ...

  7. Lua:协程,coroutine.create,coroutine.resume, coroutine.yield

    http://cloudwu.github.io/lua53doc/manual.html#2.6 Lua 支持协程,也叫 协同式多线程. 一个协程在 Lua 中代表了一段独立的执行线程. 然而,与多 ...

  8. python yield 协程_用yield实现python协程

    刚刚介绍了pythonyield关键字,趁热打铁,现在来了解一下yield实现协程. 引用官方的说法: 与线程相比,协程更轻量.一个python线程大概占用8M内存,而一个协程只占用1KB不到内存.协 ...

  9. 11.python之线程,协程,进程,

    一,进程与线程 1.什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行 ...

最新文章

  1. keras cnn注意力机制_TensorFlow、PyTorch、Keras:NLP框架哪家强
  2. 【内网渗透】利用非常规手段突破安全策略上线CS
  3. Smooth Delivery:如何减少网络拥塞?
  4. python tkinter chk
  5. lnmp修改mysql上传大小限制_安装Linux+Nginx+MySQL+PHP(LNMP)集成环境,解除上传文件大小限制...
  6. STM32这类的芯片设计有多组VSS和VDD原因
  7. 【论文笔记】一种有效攻击BERT等模型的方法
  8. IOTOS物联中台对接海康安防平台(iSecure Center)门禁系统
  9. dlib实现人脸对齐方法
  10. Android自定义方式设置drawableleft图片大小
  11. 如何调用CAD命令行命令?
  12. python关键词排名批量查排名_Python 批量获取Baidu关键词的排名并入库
  13. 再掀融资潮 团购网仍后劲不足(团购现状分析)
  14. Nice Garland
  15. 大部分erp系统都是java和.net甚至还有asp,为什么鲜有php的erp?
  16. 【MySQL】JDBC编程
  17. TCP/IP 完成端口
  18. 二进制部署Prometheus及监控服务
  19. 什么高大填空四个字动人_照样子填空填四字成语什么什么什么地想
  20. qq2007bate3协议分析

热门文章

  1. 如何最简单、通俗地理解Python的pandas库?
  2. 深聊测试开发之:从订单支付流程来聊一聊,如何预防重复支付,建议收藏。
  3. C语言倒序输出/C语言倒置输出
  4. 什么是bug?如何描述一个bug?
  5. python中time库的时间单位是秒
  6. gpu 数据库_GPU驱动的数据库可以为您做什么
  7. DDD 聚合根 限界上下文
  8. speedoffice表格中怎样给文字添加删除线?
  9. UL测试报告办理流程,UL1017测试范围有那些?
  10. 编译,发版,链接库问题