我是說swoole


底下的同學們請認真審題,問的是 Swoole 的協程和 Go 的協程有什麼區別。

Swoole 的協程已經是不需要使用 yield 關鍵字了,與 Golang 的 go func() 的寫法一致。

具體實現上 Swoole 協程是仿製的 Erlang OTP 和 Go 的 Goroutine 的,所以在大概念上差別不是很大,幾乎完全一致。

但是內部實現上也有一些區別。htf在發布Swoole4.0之前有專門發文寫過,他也在Easyswoole和MSF等群中分享過。

Swoole4 協程與 Go 協程有哪些區別?

wiki.swoole.com

這是地址。

其中有幾大差別,我按照我自己的理解提一下:

  1. Swoole 的協程使用單進程單線程模型,Go 協程使用多進程多線程模型。這也就是在某些場景下 Swoole 反而比 Go 快的原因。當然,封裝多線程也不是什麼難事,pthread/pcntl一把梭,怕煩就用個ReactProcessManager。
  2. Swoole 的協程目前僅僅支持對於 Swoole 內部封裝 IO 操作的自動調度,而 Go 由於協程嵌入到了語言層面,所以支持對所有 IO 操作的自動調度。Swoole 有向 Go 學習的打算,但是由於時間原因,還沒有實現。(這在上面那個文章里好像沒有體現,但是我記得htf是說過這話的)
  3. 由於 PHP 的 Object 具有析構函數,所以一些 GC 的操作不需要顯式使用 defer 去做,有些 PHP 會自動幫你做,所以 PHP 中不需要過多使用 defer。
  4. 關於同時讀寫 Socket,PHP 禁止了這種操作。我個人認為 Go 允許是因為 Go 的 io.Reader 和 io.Writer 有可能是指向某個時序無關的流,於是就允許了。而 PHP 中直接寫死禁止,當然你可以用全局鎖(這文檔沒提,我可能會用鎖,就和在 Go 中並發讀寫 map 一樣,雖然現在有了 sync.Map)、Channel或內置的連接池封裝。

以及回應一下下面的朋友們,你們說的yield協程Swoole官方也有發文說過區別:

Swoole4 協程與 PHP 的 Yield/Generator 協程有什麼區別?

wiki.swoole.com

而且其實 yield 協程配合 ReactPHP/amphp 等庫用著和 nodejs 的 async/await 一樣都非常爽(

關於 @塵墜 的回答呢,說的是第一點沒錯,但是不是多核就能真並行,單核就不能。單看IO操作,二者都是能夠並行完成的。然而對於並行計算任務,Swoole的協程並不支持自動spawn新進程或開新線程,所以只會運行在一個核上。所以一般用Swoole跑計算任務還會同時使用pthreads/pcntl。

----二更

Swoole原生多線程指日可待。


php 的協程是 generator,是 stackless 協程,需要自己實現調度邏輯。php 的協程是比 js 的 async/await 還要不方便的特性。

go 的協程是 stackfull 協程。運行時自動調度,完全是用同步的方式來實現非同步效果。

漏掉了 @塵墜 提到的並行特性。

在實際生產中,我經常會同時發起多個介面調用,不同的調用互不關聯。這類場景使用 php 的協程是沒有辦法並行執行的。


Swoole4 協程與 Go 協程有哪些區別?

wiki.swoole.com

Swoole4與Go協程在設計上是完全一致的,均是stackful的,即每個協程擁有獨立的運行棧.

Swoole協程調度器使用彙編代碼(剖離自Boost的make_fcontext/jump_fcontext)切換協程上下文.

Swoole4的協程調度器是單線程的,因此不存在數據同步問題.

Swoole中一個worker進程內同一時間只會有一個協程在運行.

因此在Swoole4協程中操作全局變數和靜態變數是不需要加鎖的.

Go協程調度器是多線程的,同一時間可能會有多個協程同時執行.

Go的程序依然是類似Java的多線程模式.

因此必須對臨界資源加鎖,避免出現數據同步問題.

或者使用Go官方sync包提供的各種並發容器.

實際上Go的chan和並發容器,底層仍然使用了mutex進行鎖操作,鎖的爭搶是普遍存在的.

Swoole4由於是單線程多進程的,底層沒有使用任何mutex鎖,不存在鎖的爭搶.

同樣帶來的問題是,Swoole中沒有跨進程的超全局變數,只有進程級全局變數.

也就是說,讀寫PHP全局變數和靜態變數只在當前進程內有效.

如果需要在多個worker進程間共享數據,可以使用中間件Redis.

在Swoole中不允許多個協程同時操作同一個socket,否則會產生致命錯誤:

reading or writing of the same socket in multiple coroutines at the same time is not allowed.

使用Channel或SplQueue實現連接池,管理資源對象,就可以很好地解決此問題.

在Go中允許多個協程同時操作同一個socket,實際上這可能會存在嚴重問題.

socket讀寫操作產生並發,可能產生數據包錯亂.

Swoole中在多個協程間共用同一個協程客戶端,會導致不同協程之間發生數據錯亂.

與同步阻塞程序不同,協程是並發處理請求的.

因此同一時間可能會有很多個請求(協程)在並行處理.

一旦共用一個協程客戶端連接,就會導致不同協程之間發生數據錯亂.

如果共用的是一個同步阻塞型客戶端比如PDO連接,個人認為不會出現這個問題.

因為正在運行的協程會被PDO一系列操作阻塞,在這些阻塞操作完成之前,是無法切換到其他協程的.

Swoole服務包含多個worker進程,一個worker進程包含多個協程,多個協程在worker進程內並發執行.

因此應該避免使用全局變數和靜態變數保存協程上下文內容(寫操作).

而局部變數的值會自動保存在協程棧中,所以使用局部變數是安全的.

可以使用協程編號隔離不同協程之間的全局變數,在協程退出時清理數據.

可以使用連接編號隔離不同請求之間的全局變數,在請求開始時重置數據.

不過,這要求開發者清楚自己的行為,否則可能會產生邏輯錯誤.

Swoole會在SwooleHttpServer的onRequet事件回調之前自動創建一個協程,無需手動創建協程.這就意味著,每來一個請求,Swoole都會開一個協程去處理.

所以可以理解為:一個連接至少包含一個請求,一個請求至少包含一個協程.

可以給處理請求的那個協程分配一個全局數組的連接節點$app[fd][$fd]用於存儲當前請求的上下文數據.但要注意,同一個請求內的其他協程,仍有可能覆寫這個全局數組的連接節點,而導致邏輯出錯.如果使用的是回調的非同步API而不是協程,則不用擔心這個問題.因為這時,本質上一個請求內只有處理請求的那個協程,沒有其他協程.

最後,上面有些是綜合Swoole官方文檔和自己的開發經驗得出的理解,不一定正確,僅供參考.


一個可以多核真並行,一個不行


黑色高級車和白色低級車的區別(((

swoole那個基本上就是最常見的有棧協程的實現,各種語言社區里一抓一大把,主要作用是給只會/只能用PHP的程序員打雞血. go的實現則比較高級,主要優勢在於:

  1. 跑在多個系統線程上,可以充分利用多核
  2. 讓出執行權的節點不只是io,還有函數調用


一個天生自帶,一個後天培養但是總差點意思


一個是在語言層次提供關鍵字,一個是封裝了函數。


推薦閱讀:
相关文章