工业级开源软件中的线程同步技巧
本文整理工业级开源软件中互斥锁、条件变量等同步技巧的实际应用案例。
1. Redis - 事件循环与线程安全
项目信息
- GitHub: github.com/redis/redis
- 语言: C
- 核心文件:
src/ae.c,src/networking.c,src/bio.c
使用场景:后台 I/O 线程
Redis 6.0 引入多线程 I/O,使用互斥锁+条件变量实现任务队列:
// src/bio.c - 后台 I/O 线程实现
/* 任务结构 */
struct bio_job {
time_t time;
void *arg1, *arg2, *arg3;
};
/* 每个线程一个队列 */
static pthread_mutex_t bio_mutex[REDIS_BIO_NUM_OPS];
static pthread_cond_t bio_newjob_cond[REDIS_BIO_NUM_OPS];
static list *bio_jobs[REDIS_BIO_NUM_OPS];
/* 提交任务 */
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
struct bio_job *job = zmalloc(sizeof(*job));
job->time = time(NULL);
job->arg1 = arg1;
job->arg2 = arg2;
job->arg3 = arg3;
pthread_mutex_lock(&bio_mutex[type]);
listAddNodeTail(bio_jobs[type], job);
pthread_cond_signal(&bio_newjob_cond[type]); // 通知工作线程
pthread_mutex_unlock(&bio_mutex[type]);
}
/* 工作线程主循环 */
void *bioProcessBackgroundJobs(void *arg) {
int type = (unsigned long)arg & 0xffff;
while(1) {
pthread_mutex_lock(&bio_mutex[type]);
// 等待任务
while(listLength(bio_jobs[type]) == 0) {
pthread_cond_wait(&bio_newjob_cond[type], &bio_mutex[type]);
}
// 取出任务
listNode *ln = listFirst(bio_jobs[type]);
struct bio_job *job = ln->value;
pthread_mutex_unlock(&bio_mutex[type]);
// 执行任务(在锁外执行)
// ... 具体工作
zfree(job);
}
}
设计亮点
| 技巧 | 说明 |
|---|---|
| 每线程独立队列 | 避免全局锁竞争,每个 I/O 线程有自己的任务队列 |
| 锁外执行任务 | 取出任务后立即解锁,减少临界区 |
| 条件变量通知 | 无任务时线程阻塞,避免空转 |
代码位置
redis/src/bio.c:80-150 - 任务提交与线程创建
redis/src/bio.c:150-220 - 工作线程主循环
redis/src/ae.c - 事件循环核心(单线程 Reactor)
2. Nginx - 连接池与信号量模式
项目信息
- GitHub: github.com/nginx/nginx
- 语言: C
- 核心文件:
src/core/ngx_connection.c,src/event/ngx_event.c
使用场景:连接池管理
Nginx 使用自旋锁+原子操作实现高性能连接池:
// src/core/ngx_connection.c - 连接池实现
/* 自旋锁实现(基于原子操作) */
void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin) {
while (ngx_atomic_cmp_set(lock, 0, value) == 0) {
// 自旋等待
while (*lock) {
ngx_cpu_pause(); // CPU 暂停指令,降低功耗
}
}
}
/* 连接池获取 */
ngx_connection_t *ngx_get_connection(ngx_socket_t s, ngx_log_t *log) {
ngx_connection_t *c;
// 使用自旋锁保护连接池
ngx_spinlock(&ngx_cycle->connections_mutex, ngx_pid, 1024);
c = ngx_cycle->free_connections;
if (c == NULL) {
ngx_unlock(&ngx_cycle->connections_mutex);
return NULL;
}
ngx_cycle->free_connections = c->data;
ngx_cycle->free_connection_n--;
ngx_unlock(&ngx_cycle->connections_mutex);
return c;
}
设计亮点
| 技巧 | 说明 |
|---|---|
| 自旋锁 | 短临界区使用自旋锁,避免上下文切换开销 |
| CPU 暂停指令 | ngx_cpu_pause() 降低自旋时的 CPU 占用 |
| 无锁设计 | 大部分路径使用原子操作,避免锁竞争 |
代码位置
nginx/src/core/ngx_spinlock.c:15-40 - 自旋锁实现
nginx/src/core/ngx_connection.c:1000-1100 - 连接池管理
nginx/src/event/ngx_event.c:200-300 - 事件循环
3. LevelDB - 写队列与条件变量
项目信息
- GitHub: github.com/google/leve…
- 语言: C++
- 核心文件:
db/db_impl.cc,util/env_posix.cc
使用场景:写操作批处理
LevelDB 使用条件变量实现写队列,合并多个写操作:
// db/db_impl.cc - 写操作队列
struct DBImpl::Writer {
Status status;
WriteBatch* batch;
bool done;
port::CondVar cv;
explicit Writer(port::Mutex* mu) : cv(mu), done(false) {}
};
Status DBImpl::Write(const WriteOptions& options, WriteBatch* updates) {
Writer w(&mutex_);
w.batch = updates;
MutexLock l(&mutex_);
writers_.push_back(&w);
// 等待成为队首(实现批量写入)
while (!w.done && &w != writers_.front()) {
w.cv.Wait();
}
if (w.done) {
return w.status;
}
// 合并后续写操作
WriteBatch* write_batch = BuildBatchGroup(&last_writer);
// 执行写入...
// 通知等待的写者
while (true) {
Writer* ready = writers_.front();
writers_.pop_front();
if (ready != &w) {
ready->status = status;
ready->done = true;
ready->cv.Signal(); // 通知其他写者
}
if (ready == last_writer) break;
}
// 通知新的队首
if (!writers_.empty()) {
writers_.front()->cv.Signal();
}
return status;
}
设计亮点
| 技巧 | 说明 |
|---|---|
| 批量合并 | 队首写者合并后续多个写操作,减少 I/O 次数 |
| 条件变量等待 | 非队首写者等待,避免无效竞争 |
| 级联通知 | 写入完成后级联通知后续写者 |
代码位置
leveldb/db/db_impl.cc:1100-1200 - Write 函数实现
leveldb/db/db_impl.cc:200-300 - BuildBatchGroup 批量合并
leveldb/port/port_posix.cc:50-100 - 条件变量封装
4. WebRTC - 线程安全消息队列
项目信息
- GitHub: github.com/webrtc/webr…
- 语言: C++
- 核心文件:
rtc_base/task_queue_stdlib.cc,rtc_base/thread.cc
使用场景:跨线程消息投递
WebRTC 使用标准库实现任务队列:
// rtc_base/task_queue_stdlib.cc
class TaskQueueStdlib::Impl {
public:
void PostTask(std::unique_ptr_ptr<QueuedTask> task) {
std::unique_lock<std::mutex> lock(mutex_);
queue_.push(std::move(task));
lock.unlock(); // 提前解锁
cv_.notify_one();
}
void PostDelayedTask(std::unique_ptr_ptr<QueuedTask> task, uint32_t ms) {
std::unique_lock<std::mutex> lock(mutex_);
delayed_queue_.push({
std::move(task),
clock_->CurrentTime() + TimeDelta::Millis(ms)
});
lock.unlock();
cv_.notify_one();
}
private:
void Run() {
while (running_) {
std::unique_lock<std::mutex> lock(mutex_);
// 等待任务或超时
cv_.wait(lock, [this] {
return !running_ || !queue_.empty() ||
(!delayed_queue_.empty() &&
delayed_queue_.top().time <= clock_->CurrentTime());
});
if (!running_) break;
// 处理立即执行任务
if (!queue_.empty()) {
auto task = std::move(queue_.front());
queue_.pop();
lock.unlock();
task->Run();
}
// 处理延迟任务...
}
}
std::mutex mutex_;
std::condition_variable cv_;
std::queue<std::unique_ptr_ptr<QueuedTask>> queue_;
std::priority_queue_queue<DelayedEntry> delayed_queue_;
bool running_ = true;
};
设计亮点
| 技巧 | 说明 |
|---|---|
| 提前解锁 | notify_one() 前解锁,减少唤醒线程的等待 |
| 延迟任务队列 | 使用优先队列管理定时任务 |
| 复合等待条件 | 同时检查停止标志、立即任务、延迟任务 |
代码位置
webrtc/rtc_base/task_queue_stdlib.cc:50-150 - 任务队列实现
webrtc/rtc_base/thread.cc:200-350 - 线程消息循环
webrtc/rtc_base/synchronization/mutex.h - 互斥锁封装
5. Fast-DDS(当前项目)- 定时器事件服务
项目信息
- GitHub: github.com/eProsima/Fa…
- 语言: C++
- 核心文件:
src/cpp/rtps/resources/ResourceEvent.cpp
使用场景:定时器管理
已在本文档开头详细分析,核心设计:
// 双条件变量设计
std::condition_variable cv_; // 唤醒事件服务线程
std::condition_variable cv_manipulation_; // 集合操作同步
bool allow_vector_manipulation_ = false; // 操作许可标志
设计亮点
| 技巧 | 说明 |
|---|---|
| 双条件变量 | 分离"事件调度"和"集合操作"两个关注点 |
| 时间排序 | 定时器按触发时间排序,高效获取最近触发 |
| 批量处理 | 一次唤醒处理所有到期定时器 |
代码位置
src/cpp/rtps/resources/ResourceEvent.cpp:177-228 - event_service 主循环
src/cpp/rtps/resources/ResourceEvent.cpp:80-131 - unregister_timer 同步
src/cpp/rtps/resources/TimedEventImpl.cpp - 定时器实现
6. Chromium - 线程池与任务调度
项目信息
- GitHub: github.com/chromium/ch…
- 语言: C++
- 核心文件:
base/task/thread_pool/,base/synchronization/
使用场景:线程池任务调度
Chromium 的 ThreadPool 使用工作窃取+条件变量:
// base/task/thread_pool/worker_thread.cc
void WorkerThread::RunWorker() {
while (!ShouldExit()) {
std::unique_ptr<Task> task;
{
CheckedAutoLock lock(lock_);
// 等待任务或退出信号
while (!ShouldExit() && !task_) {
// 使用条件变量等待,支持超时
wake_up_event_.TimedWait(lock, GetSleepTimeout());
// 尝试从全局队列窃取任务
task = GetWorkFromGlobalQueue();
}
if (task) {
task_ = nullptr;
}
}
if (task) {
RunTask(std::move(task));
}
}
}
void WorkerThread::WakeUp() {
CheckedAutoLock lock(lock_);
wake_up_event_.Signal(); // 通知工作线程
}
设计亮点
| 技巧 | 说明 |
|---|---|
| 工作窃取 | 空闲线程从其他线程队列窃取任务,负载均衡 |
| 超时等待 | 支持动态超时,避免线程永久阻塞 |
| 退出协调 | 条件变量同时处理任务通知和线程退出 |
代码位置
base/task/thread_pool/worker_thread.cc:100-200 - 工作线程主循环
base/task/thread_pool/thread_pool_impl.cc:200-350 - 线程池实现
base/synchronization/waitable_event_posix.cc:50-150 - 条件变量封装
7. MySQL - 连接池与线程同步
项目信息
- GitHub: github.com/mysql/mysql…
- 语言: C++
- 核心文件:
sql/conn_handler/,mysys/thr_lock.c
使用场景:连接池与锁管理
MySQL 使用条件变量实现连接等待:
// sql/conn_handler/channel_info.cc
bool Channel_info::wait_for_connection(THD *thd) {
mysql_mutex_lock(&LOCK_connection_count);
// 等待连接数低于最大限制
while (connection_count >= max_connections) {
// 使用条件变量等待
mysql_cond_wait(&COND_connection_count, &LOCK_connection_count);
// 检查是否被唤醒是因为关闭服务器
if (abort_loop) {
mysql_mutex_unlock(&LOCK_connection_count);
return false;
}
}
connection_count++;
mysql_mutex_unlock(&LOCK_connection_count);
return true;
}
void Channel_info::signal_connection_exit() {
mysql_mutex_lock(&LOCK_connection_count);
connection_count--;
mysql_cond_signal(&COND_connection_count); // 通知等待的连接
mysql_mutex_unlock(&LOCK_connection_count);
}
设计亮点
| 技巧 | 说明 |
|---|---|
| 资源限制 | 使用条件变量实现连接数限制 |
| 优雅退出 | 条件变量同时处理资源可用和服务器关闭信号 |
| 精确通知 | 使用 signal 而非 broadcast,减少唤醒风暴 |
代码位置
sql/mysqld.cc:3000-3100 - 连接处理主循环
sql/conn_handler/channel_info.cc:100-200 - 连接等待
mysys/thr_lock.c:400-600 - 底层锁实现
总结对比
| 项目 | 核心技巧 | 适用场景 |
|---|---|---|
| Redis | 每线程独立队列+条件变量 | 多线程 I/O 后台任务 |
| Nginx | 自旋锁+原子操作 | 短临界区高性能场景 |
| LevelDB | 批量合并+级联通知 | 写操作批处理优化 |
| WebRTC | 提前解锁+延迟队列 | 跨线程消息+定时任务 |
| Fast-DDS | 双条件变量+时间排序 | 定时器管理 |
| Chromium | 工作窃取+超时等待 | 线程池任务调度 |
| MySQL | 资源限制+优雅退出 | 连接池管理 |
学习建议
- 从简单开始:先阅读 Redis 的
bio.c,代码简洁易懂 - 关注边界条件:观察各项目如何处理线程退出、超时、异常
- 性能对比:对比自旋锁(Nginx)vs 互斥锁(Redis)的适用场景
- 设计模式:识别"生产者-消费者"、"读者-写者"等经典模式