竞赛条件和解锁写入
Race condition and unlocked write
我有一个关于竞争条件和同时写入的问题。
我有一个类,它的对象是从不同的线程访问的。我想只根据需要计算一些值,并缓存结果。出于性能原因,我宁愿不使用锁(在有人问之前——是的,这与我的情况有关)。
这就构成了比赛条件。但是,这些对象是常量,不会被更改。因此,如果不同的线程计算要缓存的值,在我的用例中,它们保证是相同的。在不锁定的情况下写入这些值是否安全?或者,从更广泛的角度来看,从不同的线程将相同的内容写入内存而不锁定是否安全?
写入的值是bool和double类型,所讨论的体系结构可能是x86和ARM。
编辑:感谢大家的投入。我终于决定找到一种不涉及缓存的方法。这种方法看起来确实很像"破解",并且使用标志变量存在问题。
正如您所说,这是一个竞赛条件。在C++11下,从技术上讲,它是一个数据竞赛,并且是未定义的行为。值相同并不重要。
如果您的编译器支持它(例如,最近的gcc,或者带有我的Just::Thread库的gcc或MSVC),那么您可以使用std::atomic<some_pod_struct>
为数据提供原子包装(假设它是POD结构——如果不是,那么您会遇到更大的问题)。如果它足够小,那么编译器将使其无锁,并使用适当的原子操作。对于较大的结构,库将使用锁。
在没有原子操作或锁的情况下执行此操作的问题是可见性。虽然在x86或ARM的处理器级别,从多个线程/处理器向同一内存写入相同的数据(假设它确实是逐字节相同的)没有问题,但考虑到这是一个缓存,如果已经写入,我希望您希望读取此数据,而不是重新计算。因此,您需要某种标志来表示完成。除非您使用原子操作、锁或适当的内存屏障指令,否则"就绪"标志可能会在数据出现之前对另一个处理器可见。这会把事情搞砸,因为第二个处理器现在读取的是一组不完整的数据。
您可以使用非原子操作写入数据,然后为标志使用原子数据类型。在C++11下,这将生成合适的内存屏障和同步,以确保任何看到标志集的线程都可以看到数据。两个线程写入数据仍然是未定义的行为,但在实践中可能还可以。
或者,将数据存储在由执行计算的每个线程分配的堆内存块中,并使用比较和交换操作来设置原子指针变量。如果比较和交换失败,那么另一个线程会先到达那里,所以释放数据。
最终答案可能取决于您的数据结构。
在"不可移植"领域中,您可能希望研究比较和交换,大多数处理器都允许您在指针大小的实体上进行比较和交换。为了访问它,您可以使用内联汇编(在x86上,这些是lock cmpxchg
指令),或者GCC同步扩展。当看到一个未初始化的值时,每个线程都可以急切地初始化,并发出一个比较和交换来尝试设置一个值。如果比较和交换失败,这意味着另一个线程已经击败了你
不过,最终使用该操作通常相当于实现了一个自旋锁,您可能希望避免这种情况。。。
我必须首先说,使用锁定通常是正确的方法,但。。。
即使数据大于处理器的字大小,从多个线程写入同一个变量也不会是不安全的。不存在变量可能损坏的过渡状态,因为至少有一个线程将完成值的写入。其他线程不会通过拧拧相同的值来改变它。
因此,如果可以保证无论哪个线程,计算结果都是相同的,那么多个线程这样做就没有危险。在进行计算之前,只需检查一个标志("是否已计算?")。多个线程将进入值计算代码,但一旦完成,当然没有其他线程会再这样做了。显然,做同样的事情n次是浪费时间。这里的问题是,使用锁会节省你的时间吗?还是相反?只有性能测试才能给你答案。除非有其他原因不使用锁。
如果值相同,则不需要保护不同线程对POD变量的写入。不过,如果涉及到指针,那么您肯定应该进行互锁交换。
更新:为了澄清,对于您的情况,缓存和优化不会有任何不利影响,因为您在所有线程上都编写了完全相同的值。出于同样的原因,您不需要使变量volatile
。唯一可能成为问题的是,如果您的变量与机器的单词大小不一致。看见https://stackoverflow.com/a/54242/677131了解更多详细信息。默认情况下,变量会自动对齐,但您可以显式更改对齐方式。
有一种替代方法可以完全避免这个问题。由于变量将具有相同的值,所以要么在并发执行开始前预先计算它们,要么让每个线程都有自己的副本。后者的优点是在NUMA机器上提供更好的性能。
- 我应该在锁定TBitmap画布后解锁它吗
- 虚假唤醒是否会解锁所有等待线程,甚至是不相关的线程?
- c++ 为什么我不应该从不同的线程解锁互斥锁
- 在新作用域中使用unique_lock是否等效于在使用共享资源的工作结束时解锁调用
- "data race"(不是真的)在通知条件变量并解锁关联的互斥锁后
- 程序输入密码并解锁窗口7,8,10
- 在通知之前完成手动解锁
- STD :: Mutex如何在不同的线程中解锁
- 如何使用单个解锁方法(可称为读取器或写入器)实现C++读写器锁?
- 如何在C 中自动汇总日志消息并自动解锁互斥X
- 如果我们已经手动解锁了unique_lock,那么破坏时会解锁吗?
- 正在解锁手动未定义/不良设计的锁定guard
- 从C 运行代码后解锁绑定(在R中)的问题
- 在功能返回之前,可以解锁Mutex会增加并发
- 当互斥锁解锁时,它会notify_all或notify_one
- 如何确保在C ++中解锁储物柜?哪种解决方案更好
- 我应该如何在一个功能中锁定wxMutex,并在另一个功能中将其解锁
- mutex::lock() 检查一次解锁状态是否已经被另一个线程锁定?
- 在Qt 5.4中可以对互斥对象进行两次解锁吗
- C++线程:等待condition_variable后无法解锁阵列中的互斥锁