如何证明多线程代码中sum += local_sum语句需要互斥锁?
多线程累加全局变量的同步问题
问题描述
我编写了一个简单的多线程C程序,功能是读取大量数字并最终累加到全局变量sum中。每个线程会先使用local_sum变量计算局部和,再将其加到全局sum中。目前有两个疑问:
thread_runner函数中的sum += local_sum;语句是否需要加锁?我确信该语句需要加锁,但当前程序总能得到正确结果。- 若确实需要锁,如何证明该未加锁的实现是错误的?
注:仅关注互斥锁作为同步原语,忽略返回值检查。原代码如下:
#include <assert.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <time.h> void *thread_runner(void *index); // pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; long array[100000000]; long sum = 0; int main() { /* Insert values in the array */ long i = 0; for (i = 0; i < 100000000; i++) { array[i] = i+1; } /* Declare thread variables */ pthread_t t1, t2, t3, t4; int index1 = 0; int index2 = 25000000; int index3 = 50000000; int index4 = 75000000; /* Create 4 threads */ int rc = pthread_create(&t1, NULL, thread_runner, &index1); rc = pthread_create(&t2, NULL, thread_runner, &index2); rc = pthread_create(&t3, NULL, thread_runner, &index3); rc = pthread_create(&t4, NULL, thread_runner, &index4); /* Wait for threads to complete */ rc = pthread_join(t1, NULL); rc = pthread_join(t2, NULL); rc = pthread_join(t3, NULL); rc = pthread_join(t4, NULL); printf("Sum: %ld\n", sum); return 0; } void *thread_runner(void *index) { int i = 0; int i_index = *((int*)index); long local_sum = 0; for (i = i_index; i < i_index+25000000; i++) { local_sum += array[i]; } // Do i need to lock the following statement ? sum += local_sum; }
解答
结论:必须加锁
sum += local_sum;看起来是一行代码,实际是三步非原子操作:
- 读取当前全局
sum的内存值到寄存器 - 寄存器中的值加上
local_sum - 将计算结果写回全局
sum的内存地址
多线程并发执行时,若两个线程同时执行第一步(读取到同一个sum值),各自完成加法后写回,就会导致其中一个线程的累加结果被覆盖,最终sum的实际值会小于预期。
为什么当前程序总能得到正确结果?
你的测试场景不具备并发冲突条件:
- 每个线程计算局部和的耗时极长,远超过
sum += local_sum的执行时间,导致四个线程几乎是串行执行最后的累加操作,自然不会触发竞态条件。 - 线程创建、执行的顺序刚好让它们的累加操作没有重叠,相当于没用到多线程的并发特性。
如何证明未加锁实现是错误的?
可以通过以下两种方式主动触发竞态条件:
缩短线程计算时间,高频累加
把局部计算逻辑简化,让线程循环执行多次累加操作,这样多个线程会高频并发修改sum,很快就能看到结果错误。修改后的thread_runner示例:void *thread_runner(void *index) { long local_sum = 1; // 循环10万次累加,放大竞态条件 for (int i = 0; i < 100000; i++) { sum += local_sum; } return NULL; }预期结果应为
4*100000=400000,但实际运行会得到远小于该值的结果,因为大量累加操作被覆盖。在累加前插入随机延迟
在sum += local_sum;之前加入短暂随机延迟,让线程的累加操作大概率重叠,触发竞态条件。修改后的代码示例:void *thread_runner(void *index) { int i = 0; int i_index = *((int*)index); long local_sum = 0; for (i = i_index; i < i_index+25000000; i++) { local_sum += array[i]; } // 随机延迟,让线程累加操作错开 usleep(rand() % 1000); sum += local_sum; return NULL; }同时需要在
main函数开头初始化随机数种子:srand(time(NULL));,多次运行后会偶尔出现结果错误。
正确的加锁实现
取消原代码中互斥锁的注释,在累加前后加锁、解锁,保证sum += local_sum的原子性:
#include <assert.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <time.h> void *thread_runner(void *index); pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 取消注释 long array[100000000]; long sum = 0; // main函数内容不变... void *thread_runner(void *index) { int i = 0; int i_index = *((int*)index); long local_sum = 0; for (i = i_index; i < i_index+25000000; i++) { local_sum += array[i]; } pthread_mutex_lock(&lock); // 加锁 sum += local_sum; pthread_mutex_unlock(&lock); // 解锁 return NULL; }
内容的提问来源于stack exchange,提问作者Taimoor Zaeem
相关产品推荐
相关产品推荐

