多线程竞争与线程池
学习代码:threads/threads.c、threads/threads_pool.c
多线程最容易迷惑人的地方是:代码看起来是一行,CPU 执行时可能是多步。count++在 C 代码里只有一行,但底层大致要经历读取、加一、写回三个步骤。如果两个线程同时读取到旧值 50,各自加一后都写回 51,最后结果就是 51,而不是预期的 52。这就是临界区竞争。
项目里为了比较不同同步方式,写了互斥锁、自旋锁、原子操作和 CAS。CAS 的代码如下:
voidcas_inc(int*cas_count){while(1){intold=*cas_count;intnew_value=old+1;if(__sync_bool_compare_and_swap(cas_count,old,new_value)){return;}}}CAS 的思想是乐观的:先读旧值,计算新值,提交时让 CPU 判断当前值是否仍等于旧值。如果没人改过,就交换成功;如果被其他线程改过,就继续重试。它避免了阻塞,但在竞争特别激烈时会反复失败,CPU 时间会花在重试上。所以同步方案没有绝对最好,只有适用场景:临界区短可以考虑自旋或原子操作,临界区长更适合互斥锁,简单计数优先用原子操作,复杂共享结构通常需要锁保护。
线程池则解决另一个问题:不能因为有很多任务就无限创建线程。项目里的线程池由任务队列、worker 队列、互斥锁和条件变量组成:
typedefstructnManager{structnWorker*worker_head;structnTask*task_head;pthread_mutex_tmutex;pthread_cond_tcond;}ThreadPool;添加任务时,把任务插入队列并pthread_cond_signal()通知 worker;worker 没任务时在条件变量上等待,有任务时取出任务执行:
while(pool->task_head==NULL){if(worker->terminate)break;pthread_cond_wait(&pool->cond,&pool->mutex);}我的理解是,多线程编程要同时考虑正确性和资源管理。锁、CAS 解决的是共享数据正确性;线程池解决的是线程数量和任务调度。只知道创建线程,程序很容易在线程数量上失控;只知道加锁,又可能把并发性能锁没了。真正的并发设计,是在安全、性能和复杂度之间找到平衡。
学习链接: https://github.com/0voice