企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 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`实现连接池,管理资源对象,就可以很好地解决此问题。**