例如,如果我有以下代码:
SomeType obj;
void GUIThread()
{
...
while (true)
// Read and print the content of obj
...
}
void workerThread()
{
...
// Do some calculation and write the result into obj
...
}
出于性能原因,我无法在obj
上使用互斥锁或任何类似内容,而且我不需要GUIThread
打印(obj
存储正在运行的内容的准确性workerThread
的计算结果,可以视为“运行总计”。GUIThread
需要做的只是显示在workerThread
中完成计算的大致进度。我想确定的是GUIThread
和workerThread
之间的读写竞争条件不会改变obj
中存储的数据,也不会导致程序崩溃。真的吗?
P.S。 SomeType
包含内置整数类型和std::bitset<T>
,但只有前者可以同时访问。 bitset
保持GUIThread
不受影响。
P.P.S。也许这有点偏离主题...但我认为我可以将运行结果存储在workerThread
的缓存中,并且只更新实际受保护的(通过原子性或互斥锁定或其他)每个相对很长一段时间。为实现这一目标,我需要确保如果以下代码按预期工作:
struct SomeOtherType
{
int a, b, c, d; // And other primitive types
}
std::atomic<SomeOtherType> data; // Will this work?
我想知道这是否可以保护SomeOtherType
,我认为它可以,因为SomeOtherType
中只有原始类型。
答案 0 :(得分:4)
你所说的是“我正在编写具有未定义行为的代码。无论如何,它会做我想要的吗?”。唯一的答案是除了可能的编译器编写者之外没有人知道。
看起来您的代码可能会执行您想要的操作,因为它似乎是实现编译器的最简单方法。但它可能会决定你的循环之外的任何东西都不能写入该变量,因为如果它确实存在,那将是数据竞争,因此标准没有说明它将做什么,然后决定用变量替换变量程序的一部分,因为它无法更改并重新使用该内存来存储另一个指针的值,因此您的其他线程将一个完全未连接的值设置为null并在一小时后崩溃您的程序。当然不太可能,但我可以看到它是如何发生的。
未定义的行为就是 - 未定义。它确实可以做任何事情。有些事情显然比其他事情更有可能,但是你真的想冒险格式化用户的磁盘,因为你认为“未定义”并不适用于你的程序,不管风险有多小?
答案 1 :(得分:1)
据我了解,你的结构看起来像这样
struct Data {
int one;
int two;
std::bitset<SomeType> three;
}
如果您不想使用任何类型的锁,可以尝试将共享指针交换到此结构。检查您的编译器是否支持它,这是一个新功能。
std::shared_ptr<Data> dataPointer;
void GUIThread()
{
...
while (true) {
auto ptr = std::atomic_load(&dataPointer);
// Read and print the content of *ptr
...
}
void workerThread()
{
...
// Do some calculation
auto newPtr = std::make_shared<Data>();
// make the new result visible to the gui thread
std::atomic_store(&dataPointer, newPtr);
}
答案 2 :(得分:0)
严重依赖于共享数据类型。 在复杂数据类型中可能发生的是,在写入操作期间,对象短暂地变为无效(例如,分配新的内部缓冲区)。
对于简单的数据类型,例如整数,浮点数等,代码不会在大多数情况下崩溃但没有保证!。这些应该声明为volatile
,以避免编译器将它们缓存在寄存器中。强制使用内存屏障可以提高结果的准确性。
如果你只是检查一个布尔标志或类似的东西,你就可以逃脱。
如果obj是线程安全的,那么你也可以。
我的建议,如果有疑问,请使用低开销锁,例如自旋锁。