1. 程式人生 > 實用技巧 >PHP進階教程-PHP的協程怎麼玩?這一篇帶你搞定swoole協程

PHP進階教程-PHP的協程怎麼玩?這一篇帶你搞定swoole協程

協程說複雜不復雜說難也不難,一句話可以概括:能提高併發,但不能加速任務,同步程式碼實現非同步IO,非同步非阻塞的程式碼塊。

協程是一種特殊函式,是一種可以掛起的函式,然後可以從掛起的地方重新恢復執行,一個執行緒內的多個協程是序列的,跟CPU處理程序一樣,同一時刻只能一個協程線上程上執行,除非出讓了控制權給別的協程執行。協程無法利用多核CPU因此協程只能解決併發問題,不能解決任務處理速度問題。協程就是把一個大任務再分成更小的片段,封裝程一個函式,當其中一個協程需要IO阻塞的時候,主動掛起當前協程,把控制權交給其他協程執行。

我們知道程序和執行緒是由作業系統排程的,什麼時候執行取決於作業系統什麼時候把CPU時間交給某個程序或者執行緒,而協程是什麼時候交出控制權是由使用者決定的。程序和執行緒屬於核心態,協程屬於使用者態執行緒。

協程是一種使用者態的輕量級執行緒,協程的排程完全由使用者控制。協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧,直接操作棧則基本沒有核心切換的開銷,可以不加鎖的訪問全域性變數,所以上下文的切換非常快。

我的 企鵝群 一起交流哦

協程特點

  • 使用者態執行緒、遇到IO主動讓出控制權

  • 多個協程程式碼依然是序列的,無需加鎖

  • 開銷低,只佔用記憶體,不存在程序、執行緒切換開銷

  • 併發量大,單個程序可開啟50w個協程

  • 隨時隨地,只要想併發,就呼叫go建立協程

我們知道執行緒是輕量級的程序,那麼協程就是輕量級的執行緒。協程執行線上程之上,一個執行緒可以有多個協程。

我們知道在程序遇到阻塞的時候開多一個執行緒在程序內部切換,避免每次都切換程序,這樣可以更大力度的使用CPU分給這個程序的可使用時間。而協程跟執行緒和程序的關係很類似,只不過協程是跟執行緒直接建立關係。

上圖是多個執行緒之間切換的示意圖,那麼我們來考慮一下,如果執行緒只是等待IO操作(網路或者檔案),那麼為什麼像執行緒重複使用程序一樣來重複的使用這個執行緒呢?我們把IO去掉,看看這個圖是什麼樣子的。

去掉IO部分操作,可以看出來基本上這個併發請求應用程式程式碼可以在 單個執行緒中 執行,協程最大力度的利用了執行緒等待IO的時間,讓程式在等待IO的時候可以執行別的業務程式碼。

看著像不像一個執行緒的執行流程,這就是協程的魅力所在,當一個協程被yield之後會被掛起,把控制權轉移給執行緒內部的其他協程,因為是線上程上進行的切換,所以開銷遠遠比程序和執行緒低很多。

當程式呼叫協程之後,當前協程會主動讓出控制權交給同一個執行緒內的其他協程處理,如圖所示,開發者程式碼中需要使用IO的時候主動讓出協程的控制權給別的協程使用。

去掉IO部分再看協程的處理,直接執行的都是業務邏輯,避免遇到IO導致執行緒轉換到等待狀態,更充分的利用CPU分給這個執行緒的執行時間。

注意:協程並不能讓任務加速進行,只能執行更多工。

協程由於是建立線上程之上的,因此沒有辦法使用CPU多核心的優勢,協程適合適用於IO密集運算的場景。

協程有什麼作用?

協程是為了提高CPU使用率,避免線上程阻塞的時候大量的執行緒上下文切換。

echo "1-start\n";
sleep(1);
echo "1-end\n";
echo "2-start\n";
sleep(1);
echo "2-end\n";
echo "3-start\n";
sleep(1);
echo "3-end\n";
echo "4-start\n";
sleep(1);
echo "4-end\n";

以上程式碼的CPU使用率僅有 1%

Swoole\Runtime::enableCoroutine(true);
go(function () {
    echo "go1-start\n";
    sleep(1);
    echo "go1-end\n";
});
go(function () {
    echo "go2-start\n";
    sleep(1);
    echo "go2-end\n";
});
go(function () {
    echo "go3-start\n";
    sleep(1);
    echo "go3-end\n";
});
go(function () {
    echo "go4-start\n";
    sleep(1);
    echo "go4-end\n";
});

使用協程,成功把CPU使用率提高到了4%,這樣CPU就不需要為了IO阻塞而空跑,或者進行上下文切換。之前不是說過協程不能加速嗎?這裡使用協程之後怎麼1秒多就執行完了,跟前面的程式碼不一樣?這裡得到的時間取決於最後一個協程執行結束的時間。

協程的執行順序

Swoole\Runtime::enableCoroutine(true);
go(function(){
   sleep(2);
   echo "go1\n";
});
go(function(){
    sleep(1);
    echo "go2\n";
});
echo "main\n";

先輸出:main->go2->go1

協程之間通訊

多個協程之間通訊,採用Channel實現,多個協程協助完成共同的任務。

Swoole\Runtime::enableCoroutine(true);
$chan = new Swoole\Coroutine\Channel();
go(function () use ($chan){
    sleep(1);
    $chan->push(['name'=>'sunny']);
});

go(function() use ($chan){
    $data = $chan->pop();
    print_r($data);
});
echo "結束\n";

實戰:實現waitGroup功能

利用Swoole提供的Channel實現一個 waitGroup ,主要功能是用來等待所有協程執行完成的。

<?php
class WaitGroup{
    private $count;
    private $chan;
    public function __construct()
{
        $this->chan = new Swoole\Coroutine\Channel();
    }

    public function add(){
        $this->count++;
    }
    public function done(){
        $this->chan->push(true);
    }

    public function wait(){
        for($i=0;$i<$this->count;$i++){
            $this->chan->pop();
        }
    }

}

<?php
include 'waitgroup.php';
Swoole\Runtime::enableCoroutine(true);
echo "start".PHP_EOL;
$t = microtime(true);
go(function() use ($t){
    $wg = new WaitGroup();
    $wg->add();
    go(function() use ($t,&$wg){
        echo file_get_contents("https://www.sunnyos.com/swoole.php");
        echo "協程1:".(microtime(true)-$t).PHP_EOL;
        $wg->done();
    });
    $wg->add();
    go(function() use ($t,&$wg){
        echo file_get_contents("https://www.sunnyos.com/swoole.php");
        echo "協程2:".(microtime(true)-$t).PHP_EOL;
        $wg->done();
    });
    $wg->add();
    go(function() use ($t,&$wg){
        echo file_get_contents("https://www.sunnyos.com/swoole.php");
        echo "協程3:".(microtime(true)-$t).PHP_EOL;
        $wg->done();
    });
    $wg->wait();
    echo '全部結束:'.(microtime(true)-$t).PHP_EOL;
});
echo "end".PHP_EOL;
echo microtime(true)-$t.PHP_EOL;

程式碼中:https://www.sunnyos.com/swoole.php swoole.php 的程式碼

<?php
sleep(1);
echo "My name is Sunny\n";

休眠疫苗模擬網路請求耗時

這裡看看使用協程3個協程都進行了網路請求,每個請求耗時1秒,但是在這裡執行都時候三個請求執行完了僅耗時1.2秒,但是cpu都使用率卻使用到了6%,這說明了協程充分的使用了cpu。

點關注,不迷路

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才。之前說過,PHP方面的技術點很多,也是因為太多了,實在是寫不過來,寫過來了大家也不會看的太多,所以我這裡把它整理成了PDF和文件,如果有需要的可以

點選進入暗號: PHP+「平臺」


更多學習內容可以訪問【對標大廠】精品PHP架構師教程目錄大全,只要你能看完保證薪資上升一個臺階(持續更新)

以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務程式碼寫多了沒有方向感,不知道該從那裡入手去提升,對此我整理了一些資料,包括但不限於:分散式架構、高可擴充套件、高效能、高併發、伺服器效能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell指令碼、Docker、微服務、Nginx等多個知識點高階進階乾貨需要的可以免費分享給大家,需要的可以加入我的 PHP技術交流群