我是否需要使用原子<>如果已经受到互斥锁的保护
Do I need to use atomic<> if already guard by mutex
给定本文中的代码,只使用atomic<>
和mutex
来实现Semaphore
。
我只是好奇,既然count
已经被updateMutex
保护了,那么atomic<>
有必要吗?
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();
}
};
如果没有atomic
,我认为构造函数会出现同步问题。如果在构造之后其他线程正在使用count的赋值,则该赋值可能不可见。
如果是,那么size
呢?它是否也需要受到atomic<>
的保护?
或者atomic<>
是完全无用的,因为无论其他线程何时使用size
和count
都是可见的。
谢谢!
有多个问题被问到。所有这些都需要理解底层概念:如果一个对象是由至少一个线程写入的,而另一个线程访问(读取或写入)该线程,并且写入和访问不同步,那么就存在数据竞赛。数据竞赛的正式定义见1.10[介绍.多线程]第21段:
如果程序在不同线程中包含两个冲突的操作,则程序的执行包含数据竞赛,其中至少一个操作不是原子操作,并且两个操作都不在另一个之前发生。[…]
包含数据争用的程序具有未定义的行为,即程序需要确保它没有数据争用。现在回答不同的问题:
-
是否有必要在构造函数中使用同步?
这取决于对象在构建过程中是否可以由不同的线程同时访问。我能想象的对正在构建的对象的并发访问的唯一情况是在静态初始化期间,其中多个线程已经开始访问共享对象。由于对全局对象的构造顺序的弱约束,我无法想象无论如何都会使用全局对象,并且函数局部
static
对象的构造是由实现同步的。否则,我希望使用适当的同步机制在线程之间共享对对象的引用。也就是说,我会设计系统,使构造函数不需要同步。 -
已经有锁了。这是否意味着
count
不一定是原子。由于
count
是在获得锁之前在acquire()
函数中访问的,因此这将是对另一个线程写入的对象的非同步访问,即,您将有数据竞争,因此会有未定义的行为。count
必须是原子的。 -
size
也需要同步吗。size
成员仅在Semaphore
的构造函数中修改,通过实际使其成为const
成员来强制执行这一点可能是合理的。假设在构建过程中没有同时访问对象(请参见1。在访问CCD_ 22时不存在数据竞争的可能性。
请注意,您不应该真正无保护地使用互斥对象的lock()
和unlock()
成员。相反,您应该使用std::lock_guard<std::mutex>
或std::unique_lock<std::mutex>
,可能带有辅助块。这两个类保证了获取的锁将始终被释放。我还想知道,繁忙地等待信号量获取锁是否是正确的方法。
我认为count
成为atomic<int>
的真正原因是在mutex
保护区外的aquire()
中读取:
while (count >= size) {}
在没有atomic
的情况下,编译可以假设读取一次就足够了,并且不会轮询其他线程中更改的值。
是。有一个理论风险:
count = 0;
则构造函数中的任何一个都不会被在另一个CPU上运行的不同线程及时观察到以用于对CCD_ 33或CCD_。发生这种情况的可能性可能非常小,因为为了使用信号量对象,构造函数必须完成,并且需要另一个线程以某种方式获取该对象。
也就是说,count
占用的内存的另一个CPU视图将不会在CPU之间同步,并且另一个可以读取旧的(例如未初始化的)值。
默认情况下,在这里使用std::atomic<int>
会在负载周围生成内存屏障(在本例中是通过过载运算符)并进行存储。默认情况下,这是极端保守的。
您也可以在构造函数中锁定和解锁互斥对象以获得相同的效果,但这甚至更昂贵。
必须说,这是实现计数信号量的一种非常糟糕的方式——但这毕竟是一个采访问题,因此有很多方面。
- 如果没有malloc,链表实现将失败
- 如果我只是不访问queue_front节点的子节点,而是将它们推到队列中呢?还是BFS吗
- std::map<struct,struct>::find 找不到匹配项,但是如果我循环通过 begin() 到 end(),我在那里看到匹配项
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 设计一个只能由特定类实例化的类(如果可能的话,通过make_unique)
- 有充分的理由在h文件中使用include保护而不是cpp文件吗
- 为什么在保护模式下继承升级不起作用
- 线程,如果else语句,都是错误的上下文切换后,会发生什么
- 如何将超类的受保护成员访问到其派生类. 如果已在派生类中声明了具有相同名称的函数?
- 如果孩子不需要父母班级的所有受保护成员,这是不好的班级设计吗?
- 如果目标文件已经具有标头保护,我们是否应该用 #ifndef 来保护 #include
- 如果我使用强制转换访问受保护的成员,可能会出现什么问题?
- 我是否需要使用原子<>如果已经受到互斥锁的保护
- 如果我希望子类向父类添加功能,我是否应该保护我的属性
- 如果基类构造函数受到保护,为什么我们不能在派生类函数中创建基类对象
- 在 C++ 中,如果成员指针指向某个数据,如何保护该数据不被修改
- 如果继承类型受到保护,我可以制作基类的指针以指向派生对象吗?
- 如果使单一实例构造函数受到保护,缺点是什么 - 继承 - C++11.
- 保护C++变量不被溢出?如果值小于任何DataType的UpperBound