多线程求最大值为何未出现预期的竞态条件?
代码展示
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define SIZE 2 /* array size */ #define NUM_THREADS 2 int max_num = 0; int list[SIZE] = {1, 2}; void print_list(void); void* runner(void* param); void parallel_find_max_num(void); int main(int argc, char* argv[]) { parallel_find_max_num(); return 0; } void* runner(void* param) { int part = *(int *)param; int size = SIZE / NUM_THREADS; int start_idx = part * size; if(part == NUM_THREADS - 1) size += SIZE % NUM_THREADS; /* update max num */ for(int i = 0; i < size; i++) if(max_num < list[start_idx + i]) max_num = list[start_idx + i]; } void parallel_find_max_num(void) { pthread_t tid[NUM_THREADS]; int param[2]= {0, 1}; for(int i = 0; i < NUM_THREADS; i++) pthread_create(&tid[i], NULL, runner, ¶m[i]); for(int i = 0; i < NUM_THREADS; i++) pthread_join(tid[i], NULL); printf("Max Number = %d\n", max_num); }
问题原因分析
你遇到的竞态条件触发概率极低,主要和以下几个因素有关:
线程执行逻辑极短:每个线程仅需执行几次指令就能完成任务,操作系统线程调度的开销远大于这些指令的执行时间。大多数情况下,一个线程会完整执行完毕后,另一个线程才开始运行:要么线程0先把
max_num设为1,线程1再判断1<2并更新为2;要么线程1先把max_num设为2,线程0判断2<1不成立,不会修改值。只有当操作系统恰好两个线程的关键指令间隙完成调度,才可能触发竞态。CPU存储重排序与写缓冲:现代CPU会用写缓冲优化写操作,写指令会先写入核心本地的写缓冲,再异步刷新到主内存。如果线程1先执行写2的操作,但该值还在写缓冲未同步到主内存;此时线程0读取主内存的旧值0,执行写1的操作并同步到主内存;最后线程1的写缓冲才刷新,却被线程0的写操作覆盖?不对,真正导致结果为1的极端场景是:线程1的写2操作先同步到主内存,线程0的写1操作因写缓冲延迟,在之后才刷新到主内存,覆盖了主内存中的2。这种存储重排序的概率极低,需要硬件层面的极端时序配合。
判断逻辑的特殊性:线程0处理的数值1小于线程1的2,只有当线程1的写入被线程0的写入覆盖时才会出错,反过来的情况(线程0的写入被线程1覆盖)不会产生错误结果,这进一步压缩了错误场景的出现概率。
解决方法
可以通过两种方式避免竞态:
- 给共享资源
max_num的访问加互斥锁,保证判断+写入的原子性:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define SIZE 2 /* array size */ #define NUM_THREADS 2 int max_num = 0; pthread_mutex_t mutex; int list[SIZE] = {1, 2}; void* runner(void* param); void parallel_find_max_num(void); int main(int argc, char* argv[]) { pthread_mutex_init(&mutex, NULL); parallel_find_max_num(); pthread_mutex_destroy(&mutex); return 0; } void* runner(void* param) { int part = *(int *)param; int size = SIZE / NUM_THREADS; int start_idx = part * size; if(part == NUM_THREADS - 1) size += SIZE % NUM_THREADS; for(int i = 0; i < size; i++){ pthread_mutex_lock(&mutex); if(max_num < list[start_idx + i]) max_num = list[start_idx + i]; pthread_mutex_unlock(&mutex); } return NULL; } void parallel_find_max_num(void) { pthread_t tid[NUM_THREADS]; int param[2]= {0, 1}; for(int i = 0; i < NUM_THREADS; i++) pthread_create(&tid[i], NULL, runner, ¶m[i]); for(int i = 0; i < NUM_THREADS; i++) pthread_join(tid[i], NULL); printf("Max Number = %d\n", max_num); }
- 先让每个线程计算本地区间的最大值,再用原子操作更新全局最大值,减少锁竞争:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <stdatomic.h> #define SIZE 2 /* array size */ #define NUM_THREADS 2 atomic_int max_num = 0; int list[SIZE] = {1, 2}; void* runner(void* param); void parallel_find_max_num(void); int main(int argc, char* argv[]) { parallel_find_max_num(); return 0; } void* runner(void* param) { int part = *(int *)param; int size = SIZE / NUM_THREADS; int start_idx = part * size; if(part == NUM_THREADS - 1) size += SIZE % NUM_THREADS; int local_max = 0; for(int i = 0; i < size; i++){ if(local_max < list[start_idx + i]) local_max = list[start_idx + i]; } int current_max; do { current_max = atomic_load(&max_num); } while(local_max > current_max && !atomic_compare_exchange_weak(&max_num, ¤t_max, local_max)); return NULL; } void parallel_find_max_num(void) { pthread_t tid[NUM_THREADS]; int param[2]= {0, 1}; for(int i = 0; i < NUM_THREADS; i++) pthread_create(&tid[i], NULL, runner, ¶m[i]); for(int i = 0; i < NUM_THREADS; i++) pthread_join(tid[i], NULL); printf("Max Number = %d\n", atomic_load(&max_num)); }
内容的提问来源于stack exchange,提问作者dev2022
相关产品推荐
相关产品推荐

