线程同步问题
thread synchronisation issue
在下面的例子中,我调用了最后两个线程的pthread_join()
(在我打印总和之前)。即使期望总和为0,它也会打印任何值。我知道,如果我在创建第二个线程之前做pthread_join(id1,NULL)
,那么它会工作得很好(它确实),但我不明白为什么当我在最后为两个线程调用join时它不应该工作。
因为sum只有在两个线程都完全完成执行后才会打印。因此,在第一个线程执行后,它必须在变量sum中添加2000000,而第二个线程必须从总和中减去2000000。long long sum=0;
void* counting_thread(void* arg)
{
int offset = *(int*) arg;
for(int i=0;i<2000000;i++)
{
sum=sum+offset;
}
pthread_exit(NULL);
}
int main(void)
{
pthread_t id1;
int offset1 = 1;
pthread_create(&id1,NULL,counting_thread,&offset1);
pthread_t id2;
int offset2 = -1;
pthread_create(&id2,NULL,counting_thread,&offset2);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
cout<<sum;
}
问题是sum=sum+offset;
不是线程安全的。
这导致一些总和不被计算。
正如您指定的c++, std::atomic<long long> sum;
将有所帮助,但您需要使用+=
操作符,而不是线程不安全的sum = sum + count;
sum += offset;
用互斥锁来阻止更新也是有帮助的。
如果不做这些修改,编译器可以生成
- 在函数的开头读取
sum
,只有一个线程应用它的更改。 - 为添加设置一个陈旧的
sum
值。 - 缓存状态错误。
读优化
编译器可以在线程启动时合法地读取sum的值,对其加上n次offset,并存储该值。这意味着只有一个线程可以工作。
过期值
考虑下面的汇编代码。
read sum
add offset to sum
store sum
thread1 thread2
1 read sum
2 add offset to sum read sum
3 store sum add offset to sum
4 read sum store sum
5 add offset to sum read sum
6 store sum add offset to sum
线程2的第3行将偏移量添加到旧值上,这使得线程1的第3行丢失。
缓存状态不正确
在多线程系统中,那么缓存可能在进程的线程之间不一致。
这意味着即使在sum+=offset
执行之后,另一个核心/CPU可能会看到预更新的值。
这允许cpu运行得更快,因为它们可以忽略它们之间的数据共享。但是,当两个线程访问相同的数据时,需要考虑到这一点。
std::atomic
/mutex:-
- 该值被自动修改(就好像
sum = sum + count
是不可分割的)。 - 该值在所有内核/cpu中一致可见。
- 编译器不会重新排序sum的加载/存储,就好像它不能改变一样。
您可以在没有同步的情况下得到任何结果,因为add
操作不是原子。
在基本层面
sum=sum+offset;
是
fetch sum to register # tmp := sum
add offset # tmp := tmp + offset
store new value # sum := tmp
现在想象两个线程同时工作
Thread1 Thread2 Sum
tmp:= 1 tmp:=1 1
tmp:= 1+1 tmp:=1-1 1
-zzz- sum := 0 0
sum := 2 -zzz- 2
在这一系列的计算中,线程2的减法结果丢失
如果我改变计时位
Thread1 Thread2 Sum
sum := 2 -zzz- 2
-zzz- sum := 0 0
I will get lost Thread 1 addition
添加一些优化器
现在情况更糟了。如果你不同步,编译器假定不会发生错误(因为编译器总是信任你)
所以它会跳过获取和存储部分将代码转换为
fetch sum to register # tmp := sum
add offset N times # for (i := 1 ; i < 2000000; i++) tmp := tmp + offset
store result # sum := tmp
或者
fetch sum to register # tmp := sum
add offset * N # tmp := tmp + 2000000 * offset
sore tmp # sum := tmp
现在想象两个线程在这里同时工作
添加一些与机器相关的行为
的基本思想在前面已经介绍过了,但这里不仅要归咎于编译器,还要归咎于您的平台本身。缓存机制允许更快的数据访问,但是如果缓存没有同步,不同的线程可能会读取相同变量的不同值
在并发修改全局变量sum的两个线程之间没有同步。您需要在代码周围使用互斥锁,或者您需要使用平台提供的原子自增/自减函数之一。
当你不能正确地同步线程时,这段代码就会出现"丢失的更新"问题。请参阅此链接了解Oracle术语线程干扰。https://docs.oracle.com/javase/tutorial/essential/concurrency/interfere.html他们说的是Java,但同样适用于C/c++。Sum = Sum + offset不是原子操作。大多数平台都有自动更新变量的操作,例如Windows上的InterlockedIncrement和Linux上的_sync_add_and_fetch()。
EDIT: Anthony Williams的文章"避免c++ 0x数据竞争的危险"也详细研究了这个程序。
- 线程过程中的线程同步问题
- 使用 qt 和 opengl、定时精度和垂直同步问题、c++ 显示图像
- C++11 中 3 个线程和 2 个共享资源的同步问题
- 队列的同步问题
- C++ 获取同步密钥状态状态问题
- Google 日历与订阅日历"Remember the milk"同步问题
- Qt QState机器同步问题:初始状态未在启动信号上设置
- POSIX线程同步和/或pthread_create()参数传递问题
- 线程同步问题
- MP3帧头检测FFF/FFE同步问题
- 线程同步问题
- 在同步问题中,弱指针可以代替互斥锁或临界区吗?
- 与MPI-2单向通信同步单个int值的问题
- 同步MPI-2单向通信中的顺序问题
- QStateMachine的同步问题
- c++并发、同步设计,避免多次执行问题
- 录制视频和音频-同步问题
- Poco 线程同步问题
- Boost Asio:关于教程的一些问题(同步日间服务器/客户端)
- Turbo c++文件同步问题