我是说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,还有函数调用


一个天生自带,一个后天培养但是总差点意思


一个是在语言层次提供关键字,一个是封装了函数。


推荐阅读:
相关文章