Swoole中Event::add与Event::set的区别
先看两个很容易搞混的函数:swoole_event_add和swoole_event_set。它们都是用来向Swoole事件循环注册或调整文件描述符(fd)监听行为的,语义上完全不同——如果混着用,很可能碰到回调不执行、事件丢失,甚至就给你返回一个false,你都不知道是怎么回事。 那么,要怎么区分
先看两个很容易搞混的函数:swoole_event_add和swoole_event_set。它们都是用来向Swoole事件循环注册或调整文件描述符(fd)监听行为的,语义上完全不同——如果混着用,很可能碰到回调不执行、事件丢失,甚至就给你返回一个false,你都不知道是怎么回事。

那么,要怎么区分它们?
必须先swoole_event_add,才能调swoole_event_set
很多人容易犯一个错:认为swoole_event_set也可以独立注册一个fd。实际上它只对已经在Reactor里存在的fd有作用。换句话说,这个fd要是从来没有swoole_event_add进去过,set就直接返回false,而且没有任何异常提示。这一点最坑。
- 一个常见的错误场景:写了一段
swoole_event_set($fd, $cb, null, SWOOLE_EVENT_READ),结果返回了false,还以为是参数填错了,其实是忘了先add。 - 如果你用的是Swoole v4.8及以上,可以在调用前用
swoole_event_exist($fd)检查一下。如果是v4.7或更早的版本,那就只能靠自己维护一个fd状态映射表了。 - 还有一点要注意:即使你是用
fopen或者stream_socket_client创建的资源,也必须显式地swoole_event_add,它才会真正进入事件循环的流程。
swoole_event_add是初始化,swoole_event_set是动态调整
从实际运行表现来看,swoole_event_add干的是三件事:把fd注册进epoll或kqueue、设置初始回调、启用指定事件掩码。而swoole_event_set只处理两件事:替换回调函数(前提是传了非null的值),以及开关事件监听(靠$flags参数控制)。
- 关键的不同在于:
swoole_event_add中的$write_callback和$event_flag是必填逻辑的一部分;而swoole_event_set里如果对应参数传了null,意思是不修改,而不是清空。 - 举个典型场景:MySQL异步查询做完后,先通过
swoole_event_add监听可读状态,等reap_async_query结束了,再用swoole_event_set($fd, null, null, SWOOLE_EVENT_WRITE)把监听模式切到可写,为下一次查询做准备。 - 有一个很容易踩的坑:
swoole_event_set($fd, null, null, SWOOLE_EVENT_READ)并不会清除原有的回调,它只是把可写关了、把可读打开了——之前的read_callback依然有效。
回调函数不会被swoole_event_set释放
不管你给$read_callback或$write_callback传多少次null,只要没用swoole_event_del,PHP层回调的zval就会一直被引用着,内存不会释放。闭包捕获的那些变量也会一直活着。
- 所以,在长连接场景里,如果反复用
swoole_event_set替换回调但不del,内存会一点一点涨上去,时间长了很可能是问题。 - 正确做法是:当你确定不再需要监听这个fd时,一定要调用
swoole_event_del($fd)来配对清理。 - 值得说明的是:
swoole_event_del会把读/写回调一次全释放,同时把fd从Reactor里移除,不会管你之前调了多少次set。
最后,最容易忽略的一点:事件掩码($flags)和回调函数是解耦的。你可以只改掩码不改回调,也可以只改回调不改掩码。但是,一旦掩码里没有SWOOLE_EVENT_READ,却设了$read_callback,这个回调永远不会被执行——Swoole底层只按掩码投递事件,不会校验回调是否存在。


































