- 主题:乱序执行的CPU怎么处理多线程中,共享变量有前后依赖的问题的?
比如:
线程1:
b=2;
a=1;
线程2:
while(a==1){
c=b;
}
如果a和b乱序了,线程2执行就不对了。
--
修改:wjhtingerx FROM 120.245.118.*
FROM 107.182.184.*
【 在 wjhtingerx 的大作中提到: 】
: 比如:
: 线程1:
: b=2;
: ...................
这种两个变量倚赖的, 需要使用原子变量的。
可能需要操作系统支持,windows平台有atomic变量操作的api提供。
如果想简单处理不在乎性能, 直接放到临界区里面处理, 可以把逻辑临界区里面。
不同线程进入临界执行是顺序的。
--
FROM 115.171.244.*
但实际开发过程中,好像大家基本都没理这茬,感觉也没出问题,这种到底是不是问题?
【 在 poggy 的大作中提到: 】
:
: 这种两个变量倚赖的, 需要使用原子变量的。
: 可能需要操作系统支持,windows平台有atomic变量操作的api提供。
: ...................
--
FROM 107.182.184.*
这个涉及了两个问题,一个是编译的时候a和b的赋值顺序会不会因为指令调度和源码不同,第二个是你题目里问的关于memory consistency的问题。对于memory consistency的问题,不同体系结构定义的memory model不同,像x86的类似total store order(TSO)的模型可以保证store保序,而有的模型可能就需要手动加fence.
【 在 wjhtingerx 的大作中提到: 】
: 比如:
: 线程1:
: b=2;
: ...................
--
FROM 111.206.173.*
第一个是valotile的事,这个清楚。
第二个,ARM体系,这种怎么弄的,需要编程显示的做处理吗?
【 在 ArchLinux 的大作中提到: 】
: 这个涉及了两个问题,一个是编译的时候a和b的赋值顺序会不会因为指令调度和源码不同,第二个是你题目里问的关于memory consistency的问题。对于memory consistency的问题,不同体系结构定义的memory model不同,像x86的类似total store order(TSO)的模型可以保证store保序,而有的模型可能就需要手动加fence.
:
--
FROM 107.182.184.*
【 在 wjhtingerx 的大作中提到: 】
: 比如:
: 线程1:
: b=2;
: ...................
乱序当然会发生,线程1如果改成:
d = b
b = 2
a = 1,
那么a= 1更可能先执行,线程2的程序c=b当然会执行,只不过一般后果无所谓所以你看不到啥后果。
如果a=1的时候c只能被赋值为2否则系统就爆炸,那只要运行事件够长这种逻辑运行次数够多,你一定能看到系统爆炸。
爆炸一次,你就会想办法通过互斥信号量,原子操作,读写同步(ARM上就是DMB/DSB/ISB)等指令来保证逻辑正确。
最简单的, b=2后面增加dsb指令,确保数据写执行完成以后系统才能执行后面的数据操作。
--
修改:beanspower FROM 111.197.20.*
FROM 111.197.20.*
dsb指令是系统爆炸指令?
【 在 beanspower 的大作中提到: 】
: 乱序当然会发生,线程1如果改成:
: d = b
: b = 2
: ...................
--
FROM 121.33.160.*
这种基础问题,chatgpt回答得不错
Q:
乱序执行的CPU怎么处理多线程中,共享变量有前后依赖的问题的?
比如:
线程1:
b=2;
a=1;
线程2:
while(a=1){
c=b;
}
如果a和b乱序了,线程2执行就不对了。
A:
乱序执行(Out-of-Order Execution)和多线程中的共享变量依赖问题是计算机科学中的经典问题。这个问题在多线程环境中尤其复杂,因为各个线程的执行顺序可能会互相影响。在你的例子中,如果线程1的指令出现乱序执行,而线程2正在查询变量a的值,这就有可能引起问题。
处理这个问题的一种方法是使用内存栅栏或内存屏障(Memory Barriers),它可以阻止特定的内存操作顺序被打乱。在这个例子里,如果我们在b和a之间放一个内存屏障,那么不论乱序执行如何,b=2的操作总会在a=1之前执行。
再比如,在Java中,volatile关键字也可以防止指令重排序。当一个共享变量被volatile修饰时,它会保证所有的写(write)操作都将在读(read)操作之前完成。所以在这个案例中,如果b和a都是volatile变量,那么b=2的执行必然在a=1之前。
同时,使用互斥锁(Mutex)和其他并发控制的方式也可以处理这种情况,比如,可以使用标准的多线程同步工具,比如互斥量、条件变量等等,它们会保持修改数据的原子性以及提供必要的内存栅栏。
当然,这是一个非常深入和具有挑战性的问题,在实践中需要根据具体的编程语言、硬件以及线程模型等许多因素来仔细处理。
Q:
这个是C++代码
A:
在C++中处理这种共享变量有前后依赖的问题,我们同样可以使用内存屏障(Memory Barriers)或者原子操作来解决。
C++11引入了原子类型std::atomic,这个类型可以帮助我们处理前后依赖的问题。对于std::atomic类型的变量,编译器和处理器会保证其读写操作的原子性,即这些操作不会被打断,同时也保证了内存顺序。
根据你的例子,我们可以做如下改动:
std::atomic<int> a(0);
std::atomic<int> b(0);
// 线程1
b.store(2, std::memory_order_relaxed); // 写入b
a.store(1, std::memory_order_release); // 写入a,这里使用memory_order_release,确保b.store Happens-Before a.store
// 线程2
while(a.load(std::memory_order_acquire) != 1); // 这里使用memory_order_acquire,如果a.load()操作见到了a.store()的结果,那么也一定可以见到b.store()的结果
int c = b.load(std::memory_order_relaxed); //读取b
这样编写之后,无论CPU是否对执行序列进行乱序操作,我们都可以保证线程2中的c=b执行的时候,b的值一定已经被线程1修改为2。
请注意,这也只是一种解决方案,实际的多线程编程问题需要根据具体情况进行分析和处理,可能需要更复杂的策略,如锁,条件变量等。
Q:
C++的volatile能解决这个乱序问题吗?
A:
在C++中,volatile关键词与Java中的volatile有所不同。C++中的volatile主要用于处理硬件I/O,防止编译器对volatile标记的代码进行优化处理,它并不会为你处理多线程之间的内存可见性和顺序一致性问题。
C++标准并没有规定volatile能阻止编译器和处理器对volatile标记的变量进行乱序执行或重排序操作。所以,用volatile关键字是不能保证两个线程之间的内存可见性和指令的执行顺序的。
如果你想在C++中控制内存访问的顺序,应该使用C++11引入的std::atomic和std::memory_order。这两个工具可以让你精确地控制内存访问和指令执行的顺序,而且在多线程编程中可以保证数据的原子操作和内存可见性。
--
FROM 61.48.130.*
为啥增加d = b后,a更容易乱序?
我的疑问是,这种场景应该是很多的,但是实际开发中,知道这些的程序员其实很少的,为啥也没听说爆出啥大的问题呢
【 在 beanspower 的大作中提到: 】
:
: 乱序当然会发生,线程1如果改成:
: d = b
: ...................
--
FROM 107.182.184.*
这里回答很多是错的,volatile只约束编译结果,跟CPU的乱序执行无关。
【 在 z16166 的大作中提到: 】
: 这种基础问题,chatgpt回答得不错
:
: Q:
: ...................
--
FROM 107.182.184.*