原子读操作会导致死锁吗?
Can atomic read operations lead to deadlocks?
我正在看 this Herb Sutter's talk 有关原子、互斥和内存屏障的内容,我对此有疑问。由于 47:33
Herb 解释了互斥锁和原子与内存排序的关系。他在 49:12
上说,对于默认的内存顺序,即 memory_order_seq_cst
,原子 load()
操作相当于锁定互斥锁,而原子 store
操作相当于解锁一个互斥体。在 53:15
上有人问 Herb 如果他的示例中的原子 load()
操作后面没有后续的 store()
操作会发生什么,他的回答让我很困惑:
54:00 - 如果您不写此版本(mutex.unlock()
) 或此版本(atomic.store()
), 那个人将永远没有机会 运行...
可能是因为我的英语水平不是很好,我完全误解了他,但我是这样理解他的话的:
如果一个线程 A
读取一个原子变量而没有后续写入它,没有其他线程能够使用这个变量,因为它被线程 [=21] 死锁了=] 就好像一个互斥锁被这个线程锁定了,没有进一步解锁。
但这真的是他的意思吗?这似乎不是真的。在我的理解中,释放会在加载或存储原子变量后立即自动发生。举个例子:
#include <atomic>
#include <iostream>
#include <thread>
std::atomic<int> number{0};
void foo()
{
while (number != 104) {}
std::cout << "Number:\t" << number << '\n';
}
int main()
{
std::thread thr1(foo);
std::thread thr2(foo);
std::thread thr3(foo);
std::thread thr4(foo);
number = 104;
thr1.join();
thr2.join();
thr3.join();
thr4.join();
}
在上面的示例中,有 4 个线程成功读取了同一个原子变量,并且这些线程中不需要任何写入来为其他线程释放变量。显然,atomic.load() != mutex.lock()
以及 atomic.store() != mutex.unlock()
。它们在内存屏障方面的行为可能相同,但它们并不相同,不是吗?
你能给我解释一下吗,Herb 说它们相等到底是什么意思?
这里有个误会。无论内存顺序如何,原子读取 not“等同于锁定互斥体”。就 可见性而言 它可能具有相同的效果,但互斥体要重得多。
这是一个典型的互斥锁问题:
std::mutex mtx1;
std::mutex mtx2;
void thr1() {
mtx1.lock();
mtx2.lock();
mtx2.unlock();
mtx1.unlock();
}
void thr2() {
mtx2.lock();
mtx1.lock();
mtx1.unlock();
mtx2.unlock();
}
请注意,这两个函数以相反的顺序锁定两个互斥量。所以有可能 thr1
锁定 mtx1
,然后 thr2
锁定 mtx2
,然后 thr1
尝试锁定 mtx2
并且 thr2
尝试锁定 mtx1
。这是一个僵局;两个线程都无法取得进展,因为它需要的资源被另一个线程占有。
原子没有这个问题,因为你不能运行在原子访问“内部”编写代码。你不会遇到那种资源冲突。
这个讨论背后的问题似乎是线程 运行ning while (number != 104) {}
可能看不到 number
的更新值,因此代码将是一个无限循环。这不是僵局。这并不是说这不是问题,而是可见性的问题。
我正在看 this Herb Sutter's talk 有关原子、互斥和内存屏障的内容,我对此有疑问。由于 47:33
Herb 解释了互斥锁和原子与内存排序的关系。他在 49:12
上说,对于默认的内存顺序,即 memory_order_seq_cst
,原子 load()
操作相当于锁定互斥锁,而原子 store
操作相当于解锁一个互斥体。在 53:15
上有人问 Herb 如果他的示例中的原子 load()
操作后面没有后续的 store()
操作会发生什么,他的回答让我很困惑:
54:00 - 如果您不写此版本(mutex.unlock()
) 或此版本(atomic.store()
), 那个人将永远没有机会 运行...
可能是因为我的英语水平不是很好,我完全误解了他,但我是这样理解他的话的:
如果一个线程 A
读取一个原子变量而没有后续写入它,没有其他线程能够使用这个变量,因为它被线程 [=21] 死锁了=] 就好像一个互斥锁被这个线程锁定了,没有进一步解锁。
但这真的是他的意思吗?这似乎不是真的。在我的理解中,释放会在加载或存储原子变量后立即自动发生。举个例子:
#include <atomic>
#include <iostream>
#include <thread>
std::atomic<int> number{0};
void foo()
{
while (number != 104) {}
std::cout << "Number:\t" << number << '\n';
}
int main()
{
std::thread thr1(foo);
std::thread thr2(foo);
std::thread thr3(foo);
std::thread thr4(foo);
number = 104;
thr1.join();
thr2.join();
thr3.join();
thr4.join();
}
在上面的示例中,有 4 个线程成功读取了同一个原子变量,并且这些线程中不需要任何写入来为其他线程释放变量。显然,atomic.load() != mutex.lock()
以及 atomic.store() != mutex.unlock()
。它们在内存屏障方面的行为可能相同,但它们并不相同,不是吗?
你能给我解释一下吗,Herb 说它们相等到底是什么意思?
这里有个误会。无论内存顺序如何,原子读取 not“等同于锁定互斥体”。就 可见性而言 它可能具有相同的效果,但互斥体要重得多。
这是一个典型的互斥锁问题:
std::mutex mtx1;
std::mutex mtx2;
void thr1() {
mtx1.lock();
mtx2.lock();
mtx2.unlock();
mtx1.unlock();
}
void thr2() {
mtx2.lock();
mtx1.lock();
mtx1.unlock();
mtx2.unlock();
}
请注意,这两个函数以相反的顺序锁定两个互斥量。所以有可能 thr1
锁定 mtx1
,然后 thr2
锁定 mtx2
,然后 thr1
尝试锁定 mtx2
并且 thr2
尝试锁定 mtx1
。这是一个僵局;两个线程都无法取得进展,因为它需要的资源被另一个线程占有。
原子没有这个问题,因为你不能运行在原子访问“内部”编写代码。你不会遇到那种资源冲突。
这个讨论背后的问题似乎是线程 运行ning while (number != 104) {}
可能看不到 number
的更新值,因此代码将是一个无限循环。这不是僵局。这并不是说这不是问题,而是可见性的问题。