摘要:粗暴地說上面的過程就算是協程的基本概念。多線程和多進程都是操作系統參與的調度,而協程是用戶自主實現的調度,協程的關鍵點實際上是用戶層實現自主調度,大概有翻身農奴把歌唱的意思。
首先是,這是我第一次把公眾號文章復制粘貼到sf.gg來。
其次是,很久很久之前,我挖了一個yield的一個坑,自己挖的坑自己填,不然遲早會把自己埋掉。
最后是,如果想看之前那個坑,請發送“yield”給文章末尾的公眾號,我開通了高大上的自動回復功能,稀罕地不得了!
PS:那篇文章中在最后我犯了一個錯誤,誤下了一個結論:foreach中不能使用send并猜測這是PHP的bug,實際上并不是,真實的原因粗暴簡單的理解就是send會讓生成器繼續執行一次導致。這件事情告訴我們:
除了裝逼之外,甩鍋也是有打臉風險的
那篇坑里,內容和你能在百毒上搜索到的大多數文章都是差不多的,不過我那篇坑標題起得好:《yield是個什么玩意(上)》,也就是暗示大家還有下篇,所以起標題也是需要一定技術含量的。
我堅信,在座的各位辣雞在看完上篇坑文后最想說的注定是泰迪熊這句話(這是文化屬性,不以各位的意志而轉移):
回到今天主旨上來,強調幾點:
雖然文章標題中有“yield和協程”這樣的關鍵字,但實際上yield并不是協程,看起來有不少人直接將yield和協程劃了等號。yield的本質是生成器,英文名字叫做Generator。
yield只能用在function中,但用了yield就已經不是傳統意義上的function了,同時如果你企圖在function之外的其他地方用yield,你會被打臉。
yield的最重要作用就是:自己中斷一坨代碼的執行,然后主動讓出CPU控制權給路人甲;然后又能通過一些方式從剛才中斷的地方恢復運行。這個就比較屌了,假如你請求了一個費時10s的服務器API,此時是可以讓出CPU給路人甲。粗暴地說上面的過程就算是協程的基本概念。
多線程和多進程都是操作系統參與的調度,而協程是用戶自主實現的調度,協程的關鍵點實際上是“用戶層實現自主調度”,大概有“翻身農奴把歌唱”的意思。
下面我通過一坨代碼來體會一把“翻身農奴”,你們感受一下:
current(); // 這會兒我可以讓task2介入進來了 echo $task2->current(); // task1恢復中斷 $task1->next(); // task2恢復中斷 $task2->next(); }
上面代碼執行結果如下圖:
雖然我話都說到這里了,但是肯定還是有人get不到“所以,到底發生了什么?”。你要知道,如果function gen1和function gen2中沒有yield,而是普通函數,你是無法中斷其中的for循環的,諸如下面這樣的代碼:
我似乎已然精通了yield寫到這里后我也開始蹩了,和以往的憋了三天蹦不出來個屁有所不同,我這次蹩出了一個比較典型的應用場景:curl。下面我們基于上面那坨辣雞代碼將gen1修改為一個耗時curl網絡請求,gen2將向一個文本文件中寫內容,我們的目的就是在耗時的curl開始后主動讓出CPU,讓gen2去寫文件,以實現CPU的最大化利用。
0 ); $ret = curl_multi_getcontent( $ch1 ); echo $ret.PHP_EOL; return false; } function gen2() { for ( $i = 1; $i <= 10; $i++ ) { echo "gen2 : {$i}".PHP_EOL; file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND ); yield; } } $gen1 = gen1( $mh, $ch1 ); $gen2 = gen2(); while( true ) { echo $gen1->current(); echo $gen2->current(); $gen1->next(); $gen2->next(); }上面的代碼,運行以后,我們再等待curl發起請求的5秒鐘內,同時可以完成文件寫入功能,如果換做平時的PHP程序,就只能是先阻塞等待curl拿到結果后才能完成文件寫入。
文章太長,就像“老太太的裹腳布一樣,又臭又長”,所以,最后再對代碼做個極小幅度的改動就收尾不寫了!
0 ); $ret = curl_multi_getcontent( $ch1 ); echo $ret.PHP_EOL; return false; } function gen2() { for ( $i = 1; $i <= 10; $i++ ) { echo "gen2 : {$i}".PHP_EOL; file_put_contents( "./yield.log", "gen2".$i, FILE_APPEND ); $rs = yield; echo "外部發送數據{$rs}".PHP_EOL; } } $gen1 = gen1( $mh, $ch1 ); $gen2 = gen2(); while( true ) { echo $gen1->current(); echo $gen2->current(); $gen1->send("gen1"); $gen2->send("gen2"); }我們修改了內容:
將$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/1...
最近開了一個微信公眾號,所有文章都在這里
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30994.html
摘要:并發用于制定方案,用來解決可能但未必并行的問題。在協程中使用需要注意兩點使用鏈接的多個協程最終必須由不是協程的調用方驅動,調用方顯式或隱式在最外層委派生成器上調用函數或方法。對象可以取消取消后會在協程當前暫停的處拋出異常。 導語:本文章記錄了本人在學習Python基礎之控制流程篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、了解asyncio...
摘要:消息向迭代器獲取所表示的底層序列的下一個元素。為了對方法調用做出回應,迭代器可以執行任何計算來獲取或計算底層數據序列的下一個元素。這個迭代器應擁有方法,依次返回序列中的每個元素,最后到達序列末尾時產生異常。 第五章 序列和協程 來源:Chapter 5: Sequences and Coroutines 譯者:飛龍 協議:CC BY-NC-SA 4.0 5.1 引言 在這一章中,我...
摘要:函數并不是生成器協程函數自動執行的唯一方案。因為自動執行的關鍵是,必須有一種機制,自動控制生成器協程函數的流程,接收和交還程序的執行權。回調函數可以做到這一點,對象也可以做到這一點。本系列的下一篇,將介紹基于的實現的自動執行器。 PHP下的異步嘗試系列 如果你還不太了解PHP下的生成器和協程,你可以根據下面目錄翻閱 PHP下的異步嘗試一:初識生成器 PHP下的異步嘗試二:初識協程 P...
摘要:協程的基本行為協程包含四種狀態等待開始執行。協程中重要的兩個方法調用方把數據提供給協程。注意使用調用協程時會自動預激,因此與裝飾器不兼容標準庫中的裝飾器不會預激協程,因此能兼容句法。因此,終止協程的本質在于向協程發送其無法處理的異常。 導語:本文章記錄了本人在學習Python基礎之控制流程篇的重點知識及個人心得,打算入門Python的朋友們可以來一起學習并交流。 本文重點: 1、掌握協...
閱讀 1518·2021-11-18 10:02
閱讀 1657·2021-09-04 16:40
閱讀 3171·2021-09-01 10:48
閱讀 874·2019-08-30 15:55
閱讀 1853·2019-08-30 15:55
閱讀 1365·2019-08-30 13:05
閱讀 3013·2019-08-30 12:52
閱讀 1625·2019-08-30 11:24