土法炼钢兴趣小组的算法知识备份

进程模型陷阱

目录

在 Unix 编程中,fork() 是创建新进程的标准方式。然而,当 fork() 遇上 epollLibevent,事情就变得复杂了。

1. 核心问题:Fork 后的状态

当进程调用 fork() 时,子进程会继承父进程的所有文件描述符(包括 epoll 的 fd)。 但是,Libevent 的内部状态(如 event_base)在子进程中是不可用的,除非你显式地重置它。

1.1. 为什么?

2. 解决方案:event_reinit

Libevent 提供了一个专门的函数来解决这个问题:

pid_t pid = fork();
if (pid == 0) {
    // 子进程
    if (event_reinit(base) == -1) {
        perror("event_reinit failed");
        exit(1);
    }
    
    // 现在可以安全地使用 base 了
    // 注意:之前的事件可能需要重新添加
    event_base_dispatch(base);
}

2.1. event_reinit 做了什么?

  1. 关闭旧后端: 关闭继承来的 epoll fd。
  2. 创建新后端: 调用 epoll_create 创建一个新的 epoll 实例。
  3. 重新注册: 遍历 event_base 中已有的事件,将它们重新添加到新的 epoll 实例中。
  4. 重置信号: 重置信号通知管道。

3. 其他陷阱

3.1. 信号处理

fork 后,子进程继承了父进程的信号处理函数。如果你在父进程中注册了 SIGINT 处理函数,子进程收到 SIGINT 也会执行同样的代码(可能导致双重清理)。 建议: 在子进程 event_reinit 后,清空或重新注册信号事件。

3.2. Bufferevent

bufferevent 内部维护了复杂的缓冲区和状态。fork 后,父子进程共享同一个 socket fd。 * 如果你希望父子进程同时读写同一个 socket -> 不要这样做,数据会乱序。 * 通常做法是:父进程关闭 socket,子进程处理;或者反之。

4. 总结

如果你必须在 Libevent 程序中使用 fork(例如编写多进程服务器): 1. 必须在子进程的第一时间调用 event_reinit(base)。 2. 小心处理继承的文件描述符,避免父子进程同时操作同一个 socket。 3. 推荐使用多线程模型(One Loop Per Thread)替代多进程,避开这些坑。


上一篇: 04-architecture/concurrency-models.md - 并发模型架构 下一篇: 04-architecture/cpp-modern.md - C++ 现代化封装

返回 Libevent 专题索引


By .