通过检查条件并重新检查来获取锁
Posted
技术标签:
【中文标题】通过检查条件并重新检查来获取锁【英文标题】:Acquiring lock by checking against a condition and rechecking it 【发布时间】:2018-05-29 06:36:18 【问题描述】:这样的事情是否有效:
std::vector<std::vector<int>> data;
std::shared_mutex m;
...
void Resize()
// AreAllVectorsEmpty: std::all_of(data.begin(), data.end(), [](auto& v) return v.empty();
if (!AreAllVectorsEmpty())
m.lock();
if (!AreAllVectorsEmpty())
data.resize(new_size);
m.unlock();
我正在检查AreAllVectosEmpty()
,然后如果条件成功,则获取锁定,然后再次检查相同的条件是否进行调整大小。
这会是线程安全的吗? Resize
只被一个线程调用,其他线程操作data
的元素。
AreAllVectorsEmpty
是否要求有内存栅栏或获取语义?
编辑:当m.lock
被Resize
获取时,其他线程当然会阻塞。
编辑:我们还假设new_size
足够大,可以进行重新分配。
编辑:更新 shared_mutex 的代码。
编辑:AreAllVectorsEmtpy
正在迭代数据向量。没有其他人修改数据向量,但数据[0]、数据[1]等被其他线程修改。我的假设是因为 data[0] 的 size 变量在向量内并且是一个简单的整数,因此可以安全地访问 data[0].size()、data[1].size() 等... Resize
线程。 AreAllVectorsEmpty
正在迭代 data
并检查 vector.empty()
。
【问题讨论】:
如果这个函数不修改共享资源,它是线程安全的 @mangusta 它正在修改data
向量。
m.resize
?不是data.resize
?
@WernerHenze 已修复。谢谢。
不要调用mutex.lock()
,而是使用std::unique_lock
【参考方案1】:
我会使用 shared_mutex 并使用:
只读取向量(同时读取向量)的所有线程中的共享锁 调整矢量大小时此线程中的唯一锁我认为首先检查大小,然后调整大小是安全的,前提是这是修改向量内容的唯一线程。
锁自动意味着内存屏障,否则锁没有多大意义。
【讨论】:
我正在使用shared_mutex
。为了示例的简单性,我将其更改为mutex
。无论如何,是的 Resize
被一个线程调用。【参考方案2】:
答案完全取决于AreAllVectorsEmpty
的实现方式。
如果它只是检查一个可以自动设置的标志,那么是的,它是安全的。如果它遍历您打算更改的向量(或其他常用容器),那么不,它是不安全的(如果向量在内部重新分配,迭代器会发生什么???)。
如果是后者,你需要一个读/写锁机制,看看shared mutexes。
然后您将在检查之前获取共享锁,在修改的情况下获取排他锁。
请注意,如果areAllVectorsEmpty
使用一些独立的数据结构(除了提到的原子标志之外),您可能还必须使用单独的互斥锁来保护它以及。
【讨论】:
它正在遍历数据向量。没有其他人修改数据向量,但数据[0]、数据[1]等被其他线程修改。我的假设是因为 data[0] 的 size 变量在向量内部并且是一个简单的整数,因此可以安全地访问 data[0].size()、data[1].size() 等...Resize
线程。 AreAllVectorsEmpty
正在迭代 data
并检查 vector.empty()
。
好吧,那么:如果你不保护 data
本身的迭代,任何线程都可能获得锁而另一个线程仍然迭代 - 然后调整大小可能会使后者的迭代器无效!另外:请注意std::vector::size
通常实现为end() - begin()
,而std::vector::empty
再次实现为size() == 0
。您不能依赖它们是线程安全的,因此您甚至可能必须适当地保护内部向量免受并发读/写冲突!
调整数据向量的大小仅由一个线程完成,该线程已调用 Resize。问题上也提到了
良好的一般建议:永远不要依赖假设,如果它们不正确,您可能会失败!
一些例子:向量中有4个元素,线程A开始迭代并到达元素0和1。然后线程B中断A,并且由于读取不受保护,可以获得锁。当然,它是现在唯一进行修改的线程,但线程 A 正处于其迭代的中间!它的迭代器会发生什么?【参考方案3】:
标准似乎没有要求这样做,比较http://en.cppreference.com/w/cpp/container#Thread_safety。如果它适用于您的特定编译器和 STL?您需要查看来源。但我不会依赖它。
这让我想到了一个问题:你为什么要这样做?出于性能原因?你测量过性能吗?在调用AreAllVectorsEmpty
之前锁定是否真的对性能造成了可衡量的影响?
顺便说一句,请不要直接锁定互斥锁,请使用std::lock_guard。
【讨论】:
【参考方案4】:// AreAllVectorsEmpty: std::all_of(data.begin(), data.end(), [](auto& v) 返回 v.empty();
您正在访问内部向量的内部(调用空),同时另一个线程可以将一些元素插入到其中一个内部向量中 -> 数据竞争
【讨论】:
以上是关于通过检查条件并重新检查来获取锁的主要内容,如果未能解决你的问题,请参考以下文章