PHP的协程跟GO的协程实现有什么区别?
我是说swoole
底下的同学们请认真审题,问的是 Swoole 的协程和 Go 的协程有什么区别。
Swoole 的协程已经是不需要使用 yield 关键字了,与 Golang 的 go func() 的写法一致。
具体实现上 Swoole 协程是仿制的 Erlang OTP 和 Go 的 Goroutine 的,所以在大概念上差别不是很大,几乎完全一致。
但是内部实现上也有一些区别。htf在发布Swoole4.0之前有专门发文写过,他也在Easyswoole和MSF等群中分享过。
Swoole4 协程与 Go 协程有哪些区别?wiki.swoole.com这是地址。
其中有几大差别,我按照我自己的理解提一下:
- Swoole 的协程使用单进程单线程模型,Go 协程使用多进程多线程模型。这也就是在某些场景下 Swoole 反而比 Go 快的原因。当然,封装多线程也不是什么难事,pthread/pcntl一把梭,怕烦就用个ReactProcessManager。
- Swoole 的协程目前仅仅支持对于 Swoole 内部封装 IO 操作的自动调度,而 Go 由于协程嵌入到了语言层面,所以支持对所有 IO 操作的自动调度。Swoole 有向 Go 学习的打算,但是由于时间原因,还没有实现。(这在上面那个文章里好像没有体现,但是我记得htf是说过这话的)
- 由于 PHP 的 Object 具有析构函数,所以一些 GC 的操作不需要显式使用 defer 去做,有些 PHP 会自动帮你做,所以 PHP 中不需要过多使用 defer。
- 关于同时读写 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的实现则比较高级,主要优势在于:
- 跑在多个系统线程上,可以充分利用多核
- 让出执行权的节点不只是io,还有函数调用
一个天生自带,一个后天培养但是总差点意思
一个是在语言层次提供关键字,一个是封装了函数。
推荐阅读: