国产+高潮+在线,国产 av 仑乱内谢,www国产亚洲精品久久,51国产偷自视频区视频,成人午夜精品网站在线观看

關(guān)于 協(xié)程 概念的一些疑惑

查表仔

問題描述

作為一個php開發(fā),平時接觸最多的就是傳統(tǒng)fpm框架(tp、laravel等),以及守護進程框架(webman等)。

關(guān)于協(xié)程的概念,目前看到 swoole、golang 中可以實現(xiàn)。對 協(xié)程 的概念有點模糊。

為此你搜索到了哪些方案及不適用的原因

關(guān)于 進程 的概念,無論是fpm,還是守護進程 workerman,都是一個進程處理一個請求,當(dāng) 進程數(shù)量 處理不過來很多的請求的時候,會阻塞。

想知道協(xié)程這一塊是怎么處理的?我有以下猜想:

舉個例子,業(yè)務(wù)邏輯是這樣的:

一個請求過來后,首先,需要 4 秒鐘調(diào)用第三方接口A,需要 4 秒鐘調(diào)用第三方接口B,拿到A和B接口返回的數(shù)據(jù)后,需要2秒鐘進行A和B接口返回數(shù)據(jù)的組裝。

我用同步處理這個場景,需要 4+4+2 = 10秒。如果我用協(xié)程,4+2=6秒 就可以完成。

在使用協(xié)程的情況下,如果我有5個進程,同時來了5個請求,單個進程里處理單個請求需要 6 秒鐘。是不是這 5個進程可以同時生成5個協(xié)程來處理呢?還是說 5個進程,同一時間內(nèi),只能有一個協(xié)程在處理?

2640 4 15
4個回答

chaz6chez

協(xié)程本身不具備并發(fā)能力,只是一種上下文(請求/響應(yīng)/執(zhí)行等)的編排方案,類似于隊列;協(xié)程還區(qū)分有棧協(xié)程(PHP中的fiber)和無棧協(xié)程(PHP中的yield);協(xié)程一般需要結(jié)合線程或者異步執(zhí)行能力才可以達到并發(fā)/并行效果。
你可以理解為,把代碼段碎片化,按照協(xié)程調(diào)度器的執(zhí)行方案進行執(zhí)行。

協(xié)程 + 協(xié)程調(diào)度器 + 協(xié)程執(zhí)行單元
消息 + 消息隊列 + 消費者

看起來和隊列很像是不是,本質(zhì)上是一樣的

  • 查表仔 2023-11-14

    您上面回復(fù)的我看明白了,您回答的角度是從它本身以及和其它相關(guān)技術(shù)結(jié)合的 大面 來看的。我是有點糾結(jié)于他的實際流程是怎么執(zhí)行的。是想知道在請求到達進程后,協(xié)程是如何處理以及掛起的,是單個進程之內(nèi)只有一個協(xié)程在處理,還是所有進程之內(nèi)只有一個協(xié)程在處理。

  • 查表仔 2023-11-14

    如果按照您上面說的,是不是 golang 和 swoole的協(xié)程調(diào)度機制也不一樣,那我上面的提問是不是得建立在某個技術(shù)棧上才能進行下一步的探討

  • chaz6chez 2023-11-14

    單個進程管理自己的協(xié)程,進程與進程之間是相互隔離的,所以Golang用的是多線程執(zhí)行單元

  • chaz6chez 2023-11-14

    是的,swoole是單個執(zhí)行單元,golang是多個執(zhí)行單元,PHP-fiber只能利用eventloop在主線程上進行切換調(diào)度

  • chaz6chez 2023-11-14

    通常來說,和linux內(nèi)核下面,進程切換的方案差不多,比如時間分片法,也就是某個執(zhí)行體如果執(zhí)行完了可以主動出讓當(dāng)前CPU,或者執(zhí)行超時了以后被強制暫停,等到分配下一個CPU時間;

    也就是說,協(xié)程也可以這樣

  • 查表仔 2023-11-14

    感謝您的回答~

  • chaz6chez 2023-11-14

    這個具體調(diào)度的實現(xiàn),每種協(xié)程調(diào)度的具體細(xì)節(jié)可能都不同,取決于具體實現(xiàn),但思路大差不差,可以關(guān)注一下;
    如果沒記錯的話,golang的協(xié)程和執(zhí)行單元是m:n,java21和swoole的協(xié)程和執(zhí)行單元是m:1;而php python等都是運用事件循環(huán)在當(dāng)前主線程內(nèi)進行調(diào)度的,與其他的用額外線程來執(zhí)行的不一樣。

  • 查表仔 2023-11-14

    m:n 和 m:1 也是相當(dāng)于單個進程內(nèi)的協(xié)程調(diào)度規(guī)則把。在單個進程內(nèi),swoole的 m:1,有多個協(xié)程,但是每次只調(diào)度一個。例如我是多核cpu,一個核心能處理兩個進程,但是這一個進程內(nèi),也就只能有1個協(xié)程在調(diào)度,雖然在很大程度上提高了請求的執(zhí)行速度,但是其余空閑的核心就利用不上了。不如 golang的 m:n ,有多個協(xié)程,又能同時調(diào)度多個,從而充分利用了cpu的核心吧?
    我對操作系統(tǒng)進程線程調(diào)度這一塊的知識掌握的太少了,得補習(xí)補習(xí)了

  • 查表仔 2023-11-14

    我測試了swoole的協(xié)程,通過它提供的 WaitGroup 和 Barrier 這種方法,對于開發(fā)難度來說,感覺是比較好上手業(yè)務(wù)邏輯的。golang這種屬于性能好,但是對于實際開發(fā),如果不了解他的調(diào)度機制及實際順序流程,感覺很容易寫bug。

  • chaz6chez 2023-11-14

    你可以這樣理解,通常來說golang下是用線程比較多,一般一個服務(wù)啟動一個進程即可,GPM模型最后會把碎片化的任務(wù)調(diào)度交給不同的線程來進行運行,這樣可以利用多核;
    (如果沒記錯的話,你可以具體查看一下swoole文檔或者源碼)swoole/swow下除了php主線程之外還會有一個協(xié)程執(zhí)行線程,會將主線程的任務(wù)碎片化調(diào)度交給執(zhí)行線程進行執(zhí)行,通常來說會在上層加上多進程模型來利用多核,也就是多個進程一托一;
    workerman如果使用fiber的話,就是主進程要處理碎片化調(diào)度和執(zhí)行協(xié)程,從始至終只有一個線程進行執(zhí)行,但是整體是多進程模型。
    進程與進程之間相互隔離,各是各的,整體看來都是可以利用多核的,只是利用的方式不一樣;整體并沒有說m:n就性能更好,因為線程和進程在內(nèi)核中的調(diào)度也存在性能損耗,也會有內(nèi)核態(tài)和用戶態(tài)的切換等,各有優(yōu)劣。

  • chaz6chez 2023-11-14

    golang其實還好,也分systemcall和netpoll,不同場景下有的會在固定線程上執(zhí)行,有的會有執(zhí)行線程的轉(zhuǎn)移切換,這個在調(diào)度器底層實現(xiàn)了,其實也不會有什么bug或者問題,對于開發(fā)者來說用起來跟m:1沒什么區(qū)別;為什么實現(xiàn)m:1而不是m:n,可能更多的層面是因為php和java都存在虛擬機,另外生態(tài)上面的復(fù)用上,為了不對虛擬機進行大規(guī)模的重寫,并利用之前的生態(tài)和模式(進程模型、bio等),從而用m:1的方式實現(xiàn)。
    多進程的資源占用上肯定是比多線程要多的,多方面考量吧

  • chaz6chez 2023-11-14

    多進程模型的話,相當(dāng)于自己的服務(wù)除了系統(tǒng)會對服務(wù)進行主進程的管理,自己的服務(wù)還需要在內(nèi)部進行子進程管理;而多線程模型就僅僅只需要在進程內(nèi)對線程管理而已,很多資源都可以復(fù)用,在界限上更符合“規(guī)范”或者“理念”;

  • chaz6chez 2023-11-14

    進程之間肯定是沒有線程之間那么方便快捷,存在一定的難度,為了簡化這些內(nèi)容,方便管理,把一些工作交給系統(tǒng)本身的能力,這樣會更健壯,開發(fā)起來也不需要一些過分的奇淫技巧。但實際上來說,都能通過一些方法實現(xiàn)想要的功能。用一句比較通俗的話來說就是線程比進程更具有“邊界感”。

  • 查表仔 2023-11-14

    上面說的都很有道理,但我水平不夠,對你上面說的部分知識點還是有點一知半解,再就是長期的開發(fā)思維都是對進程的一些操作,忽然理解起來線程的調(diào)度還是難懂。我邊學(xué)邊測試這邊的知識點,在回頭看看你上面說的,可能就好理解了,感謝您的耐心回答~

  • ikun 2023-11-15

    好文 ,@chaz6chez 在論壇寫個專欄吧 ORZ

  • chaz6chez 2023-11-15

    @ikun 之后有空出一個吧,我最近在研究mmap和apcu,準(zhǔn)備完善一下這個插件 http://wtbis.cn/plugin/133 ,讓它支持更多的功能,因為我現(xiàn)在在計劃做一個輕調(diào)度的插件,純用內(nèi)存和sqlite來支撐小型服務(wù)。

  • tomlibao 2023-11-16

    @chaz6chez 厲害,你是主開發(fā)什么語言的?

  • chaz6chez 2023-11-16

    @tomlibao 主PHP吧算是

  • 鄔綵唔惪 2023-11-17

    協(xié)程還區(qū)分有棧協(xié)程(PHP中的fiber)和無棧協(xié)程(PHP中的yield),這兩種協(xié)程有啥區(qū)別呢?為啥流行不起來呢?

  • nitron 2023-11-17

    有棧協(xié)程要保留函數(shù)調(diào)用棧用于掛起恢復(fù),會需要更多的內(nèi)存空間
    無棧協(xié)程在不改變函數(shù)調(diào)用棧的情況下,采用類似生成器的思路實現(xiàn)了上下文切換

    理論上無棧比有棧性能好,但實際使用中不需要扣資源的時候,兩者沒多大區(qū)別,有棧用起來方便點

  • 鄔綵唔惪 2023-11-17

    這兩種協(xié)程為啥流行不起來呢?是因為需要改造生態(tài)的問題嗎?

  • chaz6chez 2023-11-18

    @鄔綵唔惪 yield一直都流行,只不過圈子很小,reactphp、amphp這些都是利用yield + eventloop實現(xiàn)的類似async/await;這類組件或者框架有個缺點,不能利用php歷史積攢下來的大部分組件包,因為整個思想是NIO的也就是no-blocking I/O,而PHP整個生態(tài)主要是圍繞FPM,然后整體思想是blocking I/O的;同樣,因為yield由于無棧,在框架層面實現(xiàn)時候很多東西沒辦法實現(xiàn),所以這個圈子引入了fiber。

    還是那句話,協(xié)程本質(zhì)上不具備并發(fā)能力,本質(zhì)上是代碼執(zhí)行片段碎片化并編排的方案的一環(huán),協(xié)程+協(xié)程調(diào)度器+協(xié)程執(zhí)行單元才能實現(xiàn)具備高并發(fā)能力的方案;有的用線程,有的用事件驅(qū)動。

  • chaz6chez 2023-11-18

    只不過很多人習(xí)慣把這種方案籠統(tǒng)的稱之為“協(xié)程”

  • 鄔綵唔惪 2023-11-20

    fiber一樣不能利用php歷史積攢下來的大部分組件包吧?那樣和用yield實現(xiàn)的有啥區(qū)別呢?引入的意思是啥呢?

  • 小飛鼠 2024-04-10

    難搞哦,光是要讀懂你們的評論的意思 我就感覺很吃力了 怎么辦?

he426100
  1. 同一時間內(nèi),只能有一個協(xié)程在處理?
    這句話指的是在一個進程內(nèi)同一時間只有一個協(xié)程在處理,單個進程是可以創(chuàng)建無數(shù)個協(xié)程的,我試過在服務(wù)器上單進程創(chuàng)建1000萬個定時器;

  2. 我用同步處理這個場景,需要 4+4+2 = 10秒。如果我用協(xié)程,4+2=6秒 就可以完成。
    用協(xié)程也是同步,只不過不會阻塞了,開N個Curl,效果是“并發(fā)”N個請求,實際上還是一個個去執(zhí)行,只是發(fā)出請求后不會堵在那里等返回,跟隊列是挺像的,把任務(wù)拋給隊列,對當(dāng)前業(yè)務(wù)流程來說就是秒完成;

  3. 同時來了5個請求
    協(xié)程的話單進程就可以處理了,不需要5個進程,fpm下一執(zhí)行curl進程就堵在那里等返回,無法處理下一個請求,協(xié)程不存在的。

以上說的是swoole/swow

  • chaz6chez 2023-11-18

    @he426100 swoole/swow有額外一條線程來專門執(zhí)行協(xié)程的內(nèi)容,不阻塞當(dāng)前主線程,也就是有兩條,你在主線程上創(chuàng)建的協(xié)程會被協(xié)程線程接管

  • chaz6chez 2023-11-18

    你創(chuàng)建可以創(chuàng)建多個協(xié)程,但協(xié)程執(zhí)行單元同一時間只有一個,通過合理的調(diào)度和主線程進行配合進行執(zhí)行,從而達到高效的處理能力

賈仁

Mark

  • 小飛鼠 2024-04-10

    難搞哦,光是要讀懂你們的評論的意思 我就感覺很吃力了 怎么辦?

  • qqxxr 2024-04-10

    除了天分,那就努力,慢慢來懂

pader

首先你要搞清楚的是為什么像 php-fpm 這種東西一個進程一個線程只能處理一個請求,如果一個線程在處理一個請求時卡住了不能再處理別的請求了,那么它到底是卡在什么地方?

程序在運行的時候,籠統(tǒng)的可以分為兩個概述,一個是運算,一個是 IO。

運算

運算就是你需要強依賴 CPU 完成的事,比如在本地計算 N 個 1+1,執(zhí)行大量的 if 判斷邏輯,運行各種各樣的本地代碼,這類工作、需要程序占用本地 CPU 和內(nèi)存,在單個線程中當(dāng)它們在執(zhí)行時,這些硬件資源就是被占用著的,沒有更多的 CPU 時間來給到程序,所以在運算類任務(wù)時,線程一定是阻塞的。

就好比你個人在吃飯,在寫字,在讀書時需要你自己來干這些事,這時你是騰不出手來處理別的事,你就是被占用的。

IO

而另一種就是 IO,IO 就是程序調(diào)用外部的東西,然后就等外部的東西返回結(jié)果,在等的期間線程本身是閑著在那里的。比如你的程序調(diào)用 MySQL,程序就是通過協(xié)議(網(wǎng)絡(luò)、或者 Socket)將要執(zhí)行的語句發(fā)送給 MySQL 服務(wù)器,在 MySQL 返回數(shù)據(jù)前線程本身就是一直在等結(jié)果,自己并沒干什么。包括調(diào)用 Redis,或者發(fā)送 HTTP 請求等等,甚至包括向系統(tǒng)要求讀寫磁盤,都存在一定的等待,這個時間可能非常短,也可能非常長。想一想,如果你的程序執(zhí)行時間是 50ms,但在等待 MySQL 返回數(shù)據(jù)時就耗費了 30ms,這 30ms 的時間線程是閑著的,卻不能執(zhí)行其它的工作,是不是一種浪費。

和前面你自己吃飯,寫字的例子相反,你點了個外賣,外賣預(yù)估 30 分鐘送到你的手中,你要在等外賣送來的這 30 分鐘內(nèi)什么都不干,完全等在這里嗎?你對象喊你幫忙拿一下東西,你不回應(yīng),為什么,因為你在等外賣,你的時間被占用了?

異步

如前所述,你在等待 IO 響應(yīng)時浪費了大量的時間,甚至不對外部作出響應(yīng)。也許你也想到了,在做等待的時候完全可以把閑置的時間用來干別的事,這對人來說是很正常的,你肯定不會在那里傻等。

程序的事情是有上下依賴關(guān)系的,就好比你的計劃是等到外賣后開始吃飯,外賣點的餐還沒來,你就暫還沒法吃飯。但是你雖然暫不能吃飯,但是做與吃飯以外的事情是可以干的,比如給對象拿一下東西。

但是別忘了,程序是線性執(zhí)行的,按代碼的意思就是從上到下的執(zhí)行,下面的東西依賴上面的東西。那么有沒有一種辦法,當(dāng)程序去讀取 MySQL 數(shù)據(jù)時,數(shù)據(jù)沒返回前程序先去干別的呢,當(dāng) MySQL 返回后,再接著執(zhí)行原來要往下執(zhí)行的邏輯?有,這就是異步的概念。

異步最開始都是先給程序設(shè)定一個所謂的“回調(diào)函數(shù)”,然后就去干別的了,因為這里順序存在不確定性,有的先跑然后接下來的事情進回調(diào)了,在回調(diào)前又有些東西在跑,總之順序是亂亂的,所以就叫作了異步。

事件循環(huán)

我們再舉例子,你等外賣來后要吃飯,你還在等你的樂高玩具快遞到了后要拼樂高,你還在等這等那,如果你在等一萬件事,你還記得這些事在有結(jié)果后該干什么嗎?這么多事情你怎么去有條理的檢查它們是否有回應(yīng)了,有些事情你可能等著等著都忘記了,下一次有人給你個東西,你可能在想“這是什么?我什么時候要這個了?我接下來該干嘛??”

對于異步,這里有個重要的核心,那就是事件循環(huán)。

事件循環(huán)里面記錄了所有在等待的 IO 以及接下來的回調(diào)。這就好比你有一萬件事在等,每件事當(dāng)你要等的時候,你拿個小本子把它記下來,你在等什么,有結(jié)果后該干什么。當(dāng)你有事的時候你就做事,你一閑下來你就會去檢查小本子上的事有沒有結(jié)果了,有結(jié)果就把它要干的事情干掉,然后將這它從小本子上劃掉。還沒結(jié)果就繼續(xù)留在小本子上,下一次還會再檢查它。

這個小本子,就是一個隊列,有那么多事件在一個隊列里,有一個循環(huán)在線程一閑下來的時候就檢查它,事件循環(huán)的名字就是從此而來。

這樣一來,你所有的閑置時間都利用上來,你不會浪費每一分一秒,有一萬件事都可以在你的手上有條不紊的并發(fā)執(zhí)行著。你的效率真高!對于人來說,這可能有點不人道,太累了,人會崩潰,但是對于計算機,我們當(dāng)然是要充分的利用 CPU。

協(xié)程

好現(xiàn)在我們就要脫離舉生活中例子的概念,重新回到代碼中,在程序里異步開始時是一大堆回調(diào),這帶來了兩個大問題:

一是編程習(xí)慣被完全打亂,回調(diào)在程序中往往是一個函數(shù)或者一個閉包,導(dǎo)致我們的代碼寫起來極不舒服,每當(dāng)要等一個調(diào)用返回結(jié)果時,總要把接下來的代碼寫進另一個函數(shù)或者閉包中。不再是以前的從上到下直觀的順序,看著不舒服,維護也極為困難。

以前的代碼:

$data = $db->query("SELECT * FROM data LIMIT 1");
print_r($data);

現(xiàn)在的代碼:

$db->query("SELECT * FROM data LIMIT 1", function($data) {
    print_r($data);
})

二是可怕的回調(diào)地獄問題,當(dāng)你一套流程走下來需要等很多 IO,寫很多回調(diào)時,因為有先后順序,這些回調(diào)一層套一層,里里外外 N 多層,代碼看起來極為可怕,一眼看去,無法呼吸,無法思考。

以前的代碼:

$data = $db->query("SELECT * FROM data LIMIT 1");

$result = $cache->set("data_cache", $data);

if ($result) {
    echo "寫入數(shù)據(jù)緩存成功";
}

現(xiàn)在的代碼:

$db->query("SELECT * FROM data LIMIT 1", function($data) use ($redis) {
    $redis->set("data_cache", function($result) {
        if ($result) {
            echo "寫入數(shù)據(jù)緩存成功";
        }
    });
})

下面就輪到協(xié)程出場了,協(xié)程幫你從層層回調(diào)函數(shù)中解脫出來,再次回到以前的同步編程方式中來。以下面的偽代碼為例:

//調(diào)用 A
$cortinue->async(function() {
    $data = $db->query("SELECT * FROM data LIMIT 1");

    $result = $cache->set("data_cache", $data);

    if ($result) {
        echo "寫入數(shù)據(jù)緩存成功";
    }
});

//調(diào)用 B
$cortinue->async(function() {
    $data = $db->query("SELECT * FROM data LIMIT 1,1");

    $result = $cache->set("data_cache", $data);

    if ($result) {
        echo "寫入數(shù)據(jù)緩存成功";
    }
});

以上代碼中,當(dāng)調(diào)用 A的代碼執(zhí)行到 query() 在等待時,調(diào)用 A 的整個調(diào)用堆棧就暫停了等待 query() 返回結(jié)果,但是調(diào)用 B 中的代碼仍然在繼續(xù)跑,如果調(diào)用 B 的代碼停在某處等待 IO 返回時,調(diào)用 A 也不會受影響。這就實現(xiàn)了并發(fā),對于調(diào)用 A 和調(diào)用 B 中的代碼來講,它們自己就是同步的。

PHP

協(xié)程那么美好,它需要什么?

如你所見,上面的代碼要能夠?qū)崿F(xiàn)等待時停止在當(dāng)前調(diào)用堆棧的某處,卻不阻塞同一個線程中其它地方的調(diào)用,然后在調(diào)用完成后再恢復(fù)到那個地方繼續(xù)執(zhí)行,這個特性是需要語言支持的,從用戶代碼層面是無法實現(xiàn)這種東西的。如果語言層面沒有提供類似的支持,那么協(xié)程就無法實現(xiàn),比如 Swoole 就是通過擴展模塊來實現(xiàn),而 PHP 在 8.1 之前的版本中有一個叫生成器的東西,通過配置 yield 語法也可以實現(xiàn)(但是有點丑),而 PHP 8.1 之后增加了一個叫作 Fiber 的東西,可以做到無需添加關(guān)鍵字,隱式的有棧中斷和恢復(fù),是實現(xiàn)協(xié)程的基礎(chǔ)。

還有一點,如果底層的調(diào)用,比如向 MySQL 發(fā)請求走的 TCP 或者 Socket 調(diào)用,在語言層面本來就是阻塞的,也就是說語言不支持異步 IO,那么即使是異步或協(xié)程編程,程序在等待時也會完全暫停,無法并發(fā)。這是現(xiàn)在 PHP 的缺點,PHP 的網(wǎng)絡(luò) Socket 可以通過設(shè)定阻塞參數(shù)實現(xiàn)無阻塞 IO,但是文件讀寫 IO 還無法實現(xiàn)異步,除了像 Swoole 這種擴展層面提供異步文件 IO 支持,其它的比如 Workerman, Amp 要么在讀寫文件時阻塞當(dāng)前進程,要么在其它進程中寫,要么還是需要依賴擴展。

而有些語言,比如 Node,Go,Java,C,C++ 等等他們要么設(shè)計之初就把 IO 設(shè)計成異步的(Node、Go)、要么支持多線程,通過線程提供異步 IO 而不阻塞業(yè)務(wù)主線程。

這就是異步、協(xié)程。

異步、協(xié)程、多線程是現(xiàn)代語言幾個重要的概念和特性,PHP 在這方面有很大的不足,即使是語言層面提供了支持,異步和協(xié)程對整個生態(tài)也是一個很大的考驗,PHP 任重而道遠(yuǎn)。

Pader,2024年4月16日頭腦一熱發(fā)表于 Workerman 問答社區(qū),希望能解答你的疑問。

年代過于久遠(yuǎn),無法發(fā)表回答
??