Swoole4 协程与 Go 协程有哪些区别
Swoole4
与Go
协程在设计上是完全一致的,均是stackful
的,每个协程拥有独立的运行栈。协程调度器使用汇编代码,切换协程上下文。
Swoole4
与Go
协程在实现细节上存在一些差异。主要是以下几方面:
多线程
Swoole4
的协程调度器是单线程的,因此不存在数据同步问题,同一时间只会有一个协程在运行Go
协程调度器是多线程的,同一时间可能会有多个协程同时执行
因此在Swoole4
协程中操作全局变量是不需要加锁的。而Go
的程序由于依然是类似Java
的多线程模式,因此务必要对临界资源加锁,避免出现数据同步问题。或者使用官方sync
包提供的各种并发容器。
实际上Go
的chan
和并发容器,底层仍然使用了Mutex
进行锁操作,锁的争抢是普遍存在的。
Swoole4
由于是单线程多进程的,底层没有使用任何Mutex
锁,不存在锁的争抢。
同样带来的问题是,没有超全局变量。只有进程级全局变量,读写PHP
全局变量只在当前进程内有效。如果希望多进程共享数据,有3
种解决方案:
- 使用
Table
和Atomic
对象,或者其他共享内存数据结构 - 使用
IPC
进程间通信 - 借助存储实现数据的共享和中转,如
Redis
、MySQL
或文件操作
Defer
Swoole4
提供的defer
关键词与Go
的defer
存在差异。
Go
的defer
是绑定函数的,在当前函数退出时会执行defer
任务。这是由于Go
没有析构函数,可能会出现资源泄漏。而PHP
是有析构函数的,所有资源类对象,析构时会自动释放。
Go
func test() {
db := new(database)
close := db.connect()
defer close()
fmt.Println("query db...")
}
由于db
对象在gc
时仅释放内存,没有对应的析构函数释放连接资源,可能会产生资源泄漏。因此需要增加defer
任务关闭连接释放资源。
PHP
function test() {
$db = new Database;
$db->connect('127.0.0.1', 6379);
$db->query($sql);
}
上面代码不会产生资源泄漏,因为PHP
有基于引用计数的gc
和析构函数,在函数退出时,db
对象销毁,析构函数中会自动执行$db->close()
。使用defer
反而是多次一举。
function () {
$db = new Database;
$db->connect('127.0.0.1', 6379);
$db->query($sql);
defer(function () use ($db) {
$db->close();
});
}
- 函数退出时,
$db
对象被defer
引用了,因此不会析构 defer
任务执行完毕,引用解除,对象析构。在析构方法中,因为连接已关闭,不会再执行$db->close()
Swoole4
的defer
设计为在协程退出时一起执行,在多层函数调用嵌套中添加大量defer
任务,与Coroutine
是绑定的。在这个Coroutine
结束时,会按照先进后出的顺序,执行defer
任务。
共用 Socket 资源
在Go
的程序中,可以多个协程同时并发地去读同一个Socket
,底层完成了协程的排队和调度。
这要求开发者清楚自己的行为,否则可能会产生逻辑错误。
Go
func test() {
db := new(database)
close := db.connect()
go func(db) {
db.query(sql);
} (db);
go func(db) {
db.query(sql);
} (db);
}
Go
是允许这样操作的,实际上这个可能会存在严重问题。socket
读写操作产生并发,可能产生数据包错乱。
Swoole
中禁止了这种行为。不允许多个协程同时读取同一个Socket
。否则会产生致命错误。
PHP
function test() {
$db = new Database;
$db->connect('127.0.0.1', 6379);
go(function () use ($db) {
$db->query($sql);
});
go(function () use ($db) {
$db->query($sql);
});
}
以上代码中有2
个协程同时操作$db
对象,可能会产生严重错误。底层会直接抛出致命错误,错误信息为:
"%s has already been bound to another coroutine#%ld,
reading or writing of the same socket in multiple coroutines at the same time is not allowed."
错误码:SW_ERROR_CO_HAS_BEEN_BOUND
使用Channel
或SplQueue
实现连接池,管理资源对象,就可以很好地解决此问题。