pthreads共享数据访问问题:代码输出分析与同步方案
Pthreads多线程练习题答疑
我正在学习系统编程与操作系统,遇到某机构过往面试中的pthreads练习题,经调研仍有困惑,现提出以下技术问题:
- 如下代码的输出可能有多少种取值?
- 这些取值具体是什么?
- 产生不同取值的原因是什么?
- 如何避免出现不同取值?
附代码:
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> int shared_data = 100; void* thread_function(void *args) { long temp; long *j = (long *) args; temp = shared_data; temp = temp + (long)&j; // 取线程局部变量j的地址,转为long类型后累加 shared_data = temp; return (NULL); } int main() { long i = 0; long tinfo[] = {1,2,3}; pthread_t ids[3]; for(int i=0 ;i<3;i++) { pthread_create(ids+i,NULL,&thread_function, &tinfo[i]); } for(int i=0 ;i<3;i++) { pthread_join(ids[i],NULL); } printf("shared data = %d\n",shared_data); }
问题1:输出可能有多少种取值?
一共有7种不同的可能取值。
问题2:这些取值具体是什么?
三个线程的局部变量j位于各自独立的栈空间,所以它们的地址X1、X2、X3是三个不同的数值。可能的取值包括:
- 仅单个线程的增量生效:
100+X1、100+X2、100+X3(另外两个线程的写入被最后执行的线程覆盖) - 两个线程的增量生效:
100+X1+X2、100+X1+X3、100+X2+X3(一个线程读取初始值并修改,另一个读取修改后的值再修改,第三个读取初始值并覆盖前两者的部分结果) - 三个线程的增量全部生效:
100+X1+X2+X3(三个线程依次串行执行,每个都基于前一个的修改结果更新)
问题3:产生不同取值的原因是什么?
根本原因是竞态条件(Race Condition):
- 对共享变量
shared_data的更新不是原子操作,每个线程的逻辑拆成了三步:读取shared_data到局部变量、修改局部变量、写回shared_data。 - 操作系统的线程调度是抢占式的,线程随时可能被暂停,其他线程会趁机修改
shared_data。被暂停的线程恢复后,会基于之前读取的旧值修改并写回,直接覆盖其他线程的更新,最终结果完全取决于线程的执行顺序。
问题4:如何避免出现不同取值?
要消除竞态条件,必须保证同一时间只有一个线程能修改共享变量,或者让更新操作变成原子性的,常用的解决办法有两种:
方法1:使用互斥锁
通过互斥锁锁住共享变量的访问逻辑,确保同一时间只有一个线程执行修改操作:
#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<unistd.h> int shared_data = 100; pthread_mutex_t mutex; // 定义互斥锁 void* thread_function(void *args) { long temp; long *j = (long *) args; pthread_mutex_lock(&mutex); // 进入临界区前加锁 temp = shared_data; temp = temp + (long)&j; shared_data = temp; pthread_mutex_unlock(&mutex); // 退出临界区后解锁 return (NULL); } int main() { pthread_mutex_init(&mutex, NULL); // 初始化互斥锁 long tinfo[] = {1,2,3}; pthread_t ids[3]; for(int i=0 ;i<3;i++) { pthread_create(ids+i,NULL,&thread_function, &tinfo[i]); } for(int i=0 ;i<3;i++) { pthread_join(ids[i],NULL); } printf("shared data = %d\n",shared_data); pthread_mutex_destroy(&mutex); // 销毁互斥锁 }
方法2:使用原子操作
如果编译器支持(比如GCC),可以用内置的原子操作函数直接完成累加,省去锁的开销:
void* thread_function(void *args) { long *j = (long *) args; // 原子地将shared_data加上(long)&j,直接返回更新后的值 __sync_add_and_fetch(&shared_data, (long)&j); return (NULL); }
这两种方法都能保证三个线程的增量全部生效,最终输出固定为100+X1+X2+X3。
内容的提问来源于stack exchange,提问作者NB_1907
相关产品推荐
相关产品推荐

