Swoole4 协程与 Go 协程有哪些区别
最后更新于:2022-04-02 06:30:18
# Swoole4 协程与 Go 协程有哪些区别
[TOC]
`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`实现连接池,管理资源对象,就可以很好地解决此问题。**
';