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

线程安全与锁

目录

Libevent 的设计初衷是单线程的 Reactor。但在多核时代,多线程是绕不开的话题。很多初学者因为忽视了 Libevent 的线程安全机制,导致程序出现莫名其妙的 Crash 或死锁。

1. 默认行为:非线程安全

默认情况下,Libevent 的结构体(event_base, bufferevent 等)在多线程中是不安全的。 如果你在一个线程中运行 event_base_dispatch,在另一个线程中调用 event_add,结果是未定义的(通常是崩溃)。

2. 开启线程支持

要让 Libevent 支持多线程,必须在调用任何其他 Libevent 函数之前,先初始化线程锁回调。

2.1. Pthreads (Linux/macOS)

#include <event2/thread.h>

int main() {
    // 必须最先调用!
    evthread_use_pthreads();
    
    // ... event_base_new() ...
}

2.2. Windows

#include <event2/thread.h>

int main() {
    evthread_use_windows_threads();
    // ...
}

调用这个函数后,Libevent 会分配必要的锁函数(Locking Callbacks),使得 event_basebufferevent 变成线程安全的。

3. 锁的粒度

3.1. event_base 锁

开启线程支持后,每个 event_base 都有把锁。 * 当 Loop 正在处理事件时,它持有锁。 * 当你在另一个线程调用 event_add(base, ...) 时,它会尝试获取锁。 * 通知机制: 如果 Loop 正在阻塞等待(如 epoll_wait),event_add 会通过内部的通知管道(Notification Pipe/Socketpair)唤醒 Loop,使其立即处理新添加的事件。

3.2. bufferevent 锁

每个 bufferevent 也有自己的锁。 * bufferevent_writebufferevent_read 都是原子的。 * 死锁风险: 如果你在回调函数中(此时持有锁)调用了另一个也需要锁的操作,要小心死锁。虽然 Libevent 使用的是递归锁(Recursive Lock),但逻辑上的死锁依然可能发生。

4. 最佳实践

4.1. One Loop Per Thread (推荐)

尽量避免多个线程共享同一个 event_base。 * 做法: 每个线程创建一个独立的 event_base,只处理属于自己的连接。 * 优势: 无锁竞争,性能最高,逻辑最简单。 * 交互: 线程间通信通过管道或 event_active 触发。

4.2. 跨线程操作

如果必须跨线程(例如主线程接收连接,分发给工作线程): 1. 确保已调用 evthread_use_pthreads()。 2. 确保 event_base 已开启通知功能(默认开启)。 3. 直接调用 bufferevent_socket_newevent_add 是安全的。

5. 总结

Libevent 的线程安全不是免费的午餐,它带来了锁的开销。 * 必须显式开启 (evthread_use_pthreads)。 * 推荐使用无锁架构 (One Loop Per Thread)。 * 小心回调中的锁竞争。


上一篇: 03-events/files-pipes.md - 文件与管道处理 下一篇: 04-architecture/concurrency-models.md - 并发模型架构

返回 Libevent 专题索引


By .