C++ 什么时候可以从比较交换中安全地删除内存\u顺序\u获取或内存\u顺序\u释放?
我参考了Lewiss Baker的协同程序教程中的代码 我在任何地方都使用了C++ 什么时候可以从比较交换中安全地删除内存\u顺序\u获取或内存\u顺序\u释放?,c++,c++11,concurrency,C++,C++11,Concurrency,我参考了Lewiss Baker的协同程序教程中的代码 我在任何地方都使用了lock\u.compare\u exchange\u弱(currentlock,newlock,std::memory\u order\u acq\u rel),我可以安全地将它们替换为compare\u exchange\u弱(currentlock,newlock,std::memory\u order\u release,std::memory\u order\u acquire) 我还可以看到一些例子,例如从
lock\u.compare\u exchange\u弱(currentlock,newlock,std::memory\u order\u acq\u rel)
,我可以安全地将它们替换为compare\u exchange\u弱(currentlock,newlock,std::memory\u order\u release,std::memory\u order\u acquire)
我还可以看到一些例子,例如从Lewis code的compare\u-exchange\u-strong
中删除memory\u-order\u-release
(参见reset()
函数中的compare\u-exchange\u-strong
),其中只需要std::memory\u-order\u-acquire进行比较交换(但不需要释放)。我并没有看到记忆顺序的释放从弱中移除,记忆顺序的获取也从强中移除
这让我怀疑是否有更深层次的规则我不明白
谢谢。
内存顺序获取只对读取值的操作有意义,内存顺序释放只对写入值的操作有意义。由于读-修改-写操作执行读和写操作,因此可以组合这些内存顺序,但并不总是必需的
m\u事件.m\u状态.compare\u exchange\u weak
使用memory\u order\u release
写入新值,因为它试图替换以前使用memory\u order\u acquire读取的值:
// load initial value using memory_order_acquire
void* oldValue = m_event.m_state.load(std::memory_order_acquire);
do {
...
} while (!m_event.m_state.compare_exchange_weak(oldValue, this,
std::memory_order_release,
// in case of failure, load new value using memory_order_acquire
std::memory_order_acquire));
在这种情况下,甚至根本不需要使用内存顺序获取,因为oldValue从未被取消引用,而是存储为下一个指针,也就是说,完全可以用内存顺序获取替换这两个内存顺序获取
在async\u manual\u reset\u event::set()中,情况不同:
void* oldValue = m_state.exchange(this, std::memory_order_acq_rel);
if (oldValue != this)
{
auto* waiters = static_cast<awaiter*>(oldValue);
while (waiters != nullptr)
{
// we are de-referencing the pointer read from m_state!
auto* next = waiters->m_next;
waiters->m_awaitingCoroutine.resume();
waiters = next;
}
(有关完整代码,请参阅)
关于C++内存模型的更多细节,我可以推荐我已经共同撰写的论文:谢谢你这么长的解释。我不知怎的明白了。总而言之,我可以通过以下规则微调我的锁(1)锁:使用获取成功和失败,(2)解锁:使用释放成功和获取失败(3)将共享转换为独占(sharedCount和独占锁)或将独占转换回共享(sharedCount++和独占解锁):对于这些操作,我应该使用acq_rel来表示成功,使用acq_rel来表示失败。对于锁定和解锁,失败案例总是可以被放松。您没有提供从共享到独占的转换代码,或者从共享到独占的转换代码,但是这些操作通常都非常重要,因此我不想在没有更多详细信息的情况下对所需的内存顺序进行一般性的说明。谢谢。对于那些对失败放松的人来说,推理是这样的吗?失败后,它将继续循环直到成功,并且没有任何其他内存对象需要具有“发生之前”或“发生之后”关系,因此我可以安全地应用relaxed。换言之,如果循环失败时会中断,并且之后会涉及到其他对象,那么我必须更加谨慎地应用失败的记忆顺序。我想我最初误解了全部内容。我认为比较交换操作本身的正确性取决于应用的内存顺序。事实证明,compare_交换操作本身将始终按照它的意图执行,并且只有其他放松的原子操作或非原子选项的顺序(发生之前或之后)才重要。确切地说,CAS循环中的重新加载不需要参与任何“发生之前”关系,因此可以安全地放松。内存排序仅对周围的操作排序。我建议你阅读我引用的论文。如果读完后你还有问题,请告诉我。我目前正在开发一个改进的版本,所以我可以加入一些反馈。你是专门问C++11吗?或者至少支持这些MT操作的任何版本?
struct lock {
uint64_t exclusive : 1;
uint64_t id : 48;
uint64_t shared_count : 15;
};
std::atomic<lock> lock_ { {0, 0, 0} };
bool try_lock_shared() noexcept {
lock currentlock = lock_.load(std::memory_order_acquire);
if (currentlock.exclusive == 1) {
return false;
}
lock newlock;
do {
newlock = currentlock;
newlock.shared_count++;
}
while(!lock_.compare_exchange_weak(currentlock, newlock, std::memory_order_acq_rel) && currentlock.exclusive == 0);
return currentlock.exclusive == 0;
}
bool try_lock() noexcept {
uint64_t id = utils::get_thread_id();
lock currentlock = lock_.load(std::memory_order_acquire);
if (currentlock.exclusive == 1) {
assert(currentlock.id != id);
return false;
}
bool result = false;
lock newlock { 1, id, 0 };
do {
newlock.shared_count = currentlock.shared_count;
}
while(!(result = lock_.compare_exchange_weak(currentlock, newlock, std::memory_order_acq_rel)) && currentlock.exclusive == 0);
return result;
}
// load initial value using memory_order_acquire
void* oldValue = m_event.m_state.load(std::memory_order_acquire);
do {
...
} while (!m_event.m_state.compare_exchange_weak(oldValue, this,
std::memory_order_release,
// in case of failure, load new value using memory_order_acquire
std::memory_order_acquire));
void* oldValue = m_state.exchange(this, std::memory_order_acq_rel);
if (oldValue != this)
{
auto* waiters = static_cast<awaiter*>(oldValue);
while (waiters != nullptr)
{
// we are de-referencing the pointer read from m_state!
auto* next = waiters->m_next;
waiters->m_awaitingCoroutine.resume();
waiters = next;
}
// (1) - this acquire-load synchronizes-with the release-CAS (11)
auto n = head.load(std::memory_order_acquire);
// (8) - this acquire-load synchronizes-with the release-CAS (11)
h.acquire(head, std::memory_order_acquire);
// (11) - this release-CAS synchronizes-with the acquire-load (1, 8)
if (head.compare_exchange_weak(expected, next, std::memory_order_release, std::memory_order_relaxed))