Day5 工业级开源软件中的线程同步技巧

3 阅读7分钟

工业级开源软件中的线程同步技巧

本文整理工业级开源软件中互斥锁、条件变量等同步技巧的实际应用案例。


1. Redis - 事件循环与线程安全

项目信息

使用场景:后台 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 - 连接池与信号量模式

项目信息

使用场景:连接池管理

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 - 写队列与条件变量

项目信息

使用场景:写操作批处理

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 - 线程安全消息队列

项目信息

使用场景:跨线程消息投递

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(当前项目)- 定时器事件服务

项目信息

使用场景:定时器管理

已在本文档开头详细分析,核心设计:

// 双条件变量设计
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 - 线程池与任务调度

项目信息

使用场景:线程池任务调度

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 - 连接池与线程同步

项目信息

使用场景:连接池与锁管理

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资源限制+优雅退出连接池管理

学习建议

  1. 从简单开始:先阅读 Redis 的 bio.c,代码简洁易懂
  2. 关注边界条件:观察各项目如何处理线程退出、超时、异常
  3. 性能对比:对比自旋锁(Nginx)vs 互斥锁(Redis)的适用场景
  4. 设计模式:识别"生产者-消费者"、"读者-写者"等经典模式