C++ 我是否需要使用原子<&燃气轮机;如果已经被互斥锁保护
给定中的代码,仅使用C++ 我是否需要使用原子<&燃气轮机;如果已经被互斥锁保护,c++,multithreading,c++11,C++,Multithreading,C++11,给定中的代码,仅使用原子和互斥实现信号量 我只是好奇,既然count已经被updateMutex保护了,那么原子的有必要吗 struct Semaphore { int size; atomic<int> count; mutex updateMutex; Semaphore(int n) : size(n) { count.store(0); } void aquire() { while (1) {
原子
和互斥
实现信号量
我只是好奇,既然count
已经被updateMutex
保护了,那么原子的
有必要吗
struct Semaphore {
int size;
atomic<int> count;
mutex updateMutex;
Semaphore(int n) : size(n) { count.store(0); }
void aquire() {
while (1) {
while (count >= size) {}
updateMutex.lock();
if (count >= size) {
updateMutex.unlock();
continue;
}
++count;
updateMutex.unlock();
break;
}
}
void release() {
updateMutex.lock();
if (count > 0) {
--count;
} // else log err
updateMutex.unlock();
}
};
struct信号量{
整数大小;
原子计数;
互斥更新;
信号量(intn):大小(n){count.store(0);}
虚空隔水层(){
而(1){
而(计数>=大小){}
updateMutex.lock();
如果(计数>=大小){
updateMutex.unlock();
继续;
}
++计数;
updateMutex.unlock();
打破
}
}
无效释放(){
updateMutex.lock();
如果(计数>0){
--计数;
}//日志错误
updateMutex.unlock();
}
};
如果没有原子
,我认为构造函数将出现同步问题。如果其他线程在构造之后立即使用计数赋值,则计数赋值可能不可见
如果是这样,那么大小如何?它是否也需要受到原子的保护
或者
原子
是完全无用的,因为无论其他线程何时使用它们,大小
和计数
都是可见的
谢谢 是的。理论上存在以下风险:
count=0代码>
在构造函数中,另一个CPU上运行的不同线程不会及时观察到后续调用acquire()
或release()
。发生这种情况的可能性很可能非常小,因为为了使用信号量对象,构造函数必须完成,并且不知何故,另一个线程需要获得该对象
这就是说,另一个CPU对count
占用的内存的视图不会在CPU之间同步,另一个CPU可以读取旧的(例如未初始化的)值
默认情况下,这里使用std::atomic
会在加载(在本例中是通过重载运算符)和存储周围生成内存屏障。默认情况下,这是非常保守的
您还可以在构造函数中锁定和解锁互斥锁,以获得相同的效果,但这会更加昂贵
必须说,这是一种非常糟糕的实现计数信号量的方法——但这毕竟是一个面试问题,因此有很多方面 我认为count
成为原子的真正原因是在aquire()
while (count >= size) {}
如果没有atomic
,则允许编译程序假设只读取一次就足够了,并且不会轮询其他线程中更改的值。有多个问题。所有这些都需要理解底层概念:如果一个对象由至少一个线程写入,而另一个线程访问(读或写),并且写入和访问不同步,那么就存在数据竞争。数据竞争的正式定义见1.10[简介.多线程]第21段:
如果一个程序在不同的线程中包含两个冲突的操作,则该程序的执行包含一个数据竞争,其中至少一个操作不是原子的,并且两个操作都不在另一个线程之前发生。[……]
包含数据竞争的程序具有未定义的行为,即程序需要确保其不存在数据竞争。现在回答不同的问题:
是否有必要在构造函数中使用同步
这取决于对象在构造过程中是否可以由不同线程并发访问。我能想象的唯一一种并发访问正在构造的对象的情况是在静态初始化期间,多个线程已经开始访问共享对象。由于对全局对象构造顺序的弱约束,我无法想象全局对象会被使用,而函数localstatic
对象的构造是通过实现同步的。否则,我希望使用适当的同步机制在线程之间共享对对象的引用。也就是说,我将设计系统,使构造函数不需要同步
已经有锁了。这是否意味着count
不必是原子
由于在获得锁之前,count
是在acquire()
函数中访问的,因此这将是对由另一个线程写入的对象的非同步访问,也就是说,您将有一个数据争用,因此是未定义的行为。计数必须是原子的
大小也需要同步吗
size
成员仅在Semaphore
的构造函数中修改,通过实际使其成为const
成员来强制执行这一点可能是合理的。假设对象在构造期间没有被并发访问(见上文1),那么在访问size
时就不存在数据争用的可能性
请注意,您不应该真正毫无防备地使用互斥锁的lock()
和unlock()
成员。相反,您应该使用std::lock\u guard
或std::unique\u lock
,可能还有一个辅助块。这两个类保证获得的锁将始终被释放。我还想问,等待信号量获取锁的繁忙等待是否是正确的方法。构造函数怎么会有同步问题@KerrekSB,因为如果没有设置内存障碍,写入到大小
和计数
的数据可能不会立即被其他内核上的线程看到,不是吗?其他线程怎么可能连k都看不到呢