我正在审查一些源代码,我想知道以下是否是线程安全的?我听说过编译器或CPU指令/读取重新排序(它是否与分支预测有关?),下面的Data-> unsafe_variable变量可以在任何时候被另一个线程修改。
我的问题是:根据编译器/ CPU如何重新排序读/写,下面的代码是否可以允许数据 - > unsafe_variable被提取两次? (见第2段)
注意:我不担心第一次访问,任何数据都可以存在,只要它没有通过'if',我只关心数据的可能性在'if'之后再次获取。我还想知道在这里使用volatile进行修改是否有助于防止双重抓取?
int function(void* Data) {
// Data is allocated on the heap
// What it contains at this point is not important
size_t _varSize = ((volatile DATA *)Data)->unsafe_variable;
if (_varSize > x * y)
{
return FALSE;
}
// I do not want Data->unsafe_variable to be fetch once this point reached,
// I want to use the value "supposedly" stored in _varSize
// Would any compiler/CPU reordering would allow it to be double fetched?
size_t size = _varSize - t * q;
function_xy(size);
return TRUE;
}
基本上我不希望程序出于安全原因这样做:
_varSize = ((volatile DATA *)Data)->unsafe_variable;
if (_varSize > x * y)
{
return FALSE;
}
size_t size = ((volatile DATA *)Data)->unsafe_variable - t * q;
function10(size);
我在这里简化,他们不能使用互斥锁。但是,在第一行之后使用_ReadWriteBarrier()或MemoryBarrier()而不是挥发性强制转换会更安全吗? (VS编译器)
编辑:为代码提供更多上下文。
答案 0 :(得分:4)
代码因各种原因而被破坏。我会指出其中一个更微妙的,因为其他人指出了更明显的一个。该对象不是volatile
。将指针强制转换为指向易失性对象的指针不会使对象变得易变,而只是编译器所在。
但是有一个更重要的一点 - 你正在以完全错误的方式解决这个问题。您应该检查代码是否正确,即是否保证可以正常工作。你并不聪明,没有人会想到系统可能无法按照你的假设做的一切。所以相反,不要做那些假设。
考虑CPU读取重新排序等事情完全错误。你应该期望CPU做什么,而且只需要做什么。你绝对不应该考虑它可能失败的具体机制,而只考虑它是否保证能够工作。
你正在做的就是试图弄清楚一名员工是否有保证出现工作,检查他是否有流感疫苗,检查他是否还活着,等等。你无法检查甚至想到他可能无法出现的一切可能的方式。因此,如果发现你必须检查那些类型的东西,那么它不能保证并且依赖它就会被打破。周期。
你不能通过说“CPU没有做任何可以打破这个的东西来做出可靠的代码,所以没关系”。您可以通过说“我确保我的代码不依赖于相关标准无法保证的任何内容来制作可靠的代码。”
为您提供完成工作所需的所有工具,包括内存屏障,原子操作,互斥锁等。请使用它们。
你并不聪明,无法想到一些不能保证工作的事情可能会失败。而且你有很多东西可以保证有效。修复此代码,如果可能,请与编写该代码的人员讨论使用正确的同步。
这听起来有点暴躁,我为此道歉。但是我看到太多的代码使用了这样的“技巧”,这些代码在测试机器上完美运行,但是当新的CPU出现时,新的编译器或操作系统的新版本出现故障。修复这样的代码可能会令人难以置信,因为这些黑客会隐藏实际的同步要求。正确的答案几乎总是要清楚准确地编码你真正想要的东西,而不是假设你得到它,因为你不知道你不会有任何理由。
这是痛苦经历的宝贵建议。
答案 1 :(得分:1)
标准清楚。如果任何线程可能正在修改对象,则必须同步所有线程中的所有访问,或者您有未定义的行为。
答案 2 :(得分:0)
C ++的唯一可移植解决方案是C++11 atomics,可在upcoming VS 2012中找到。
至于C,我不知道最近的C标准是否会带来一些便携式设施,我不是在关注它,但是当你使用Visal Studio时,无论如何都没关系,因为微软没有实施最新的C标准。 / p>
但是,如果您知道自己正在为Visual Studio开发,则可以依赖guarantees provided by this compiler,这适用于C和C ++。其中一些是隐式的(访问volatile变量也意味着应用了一些内存障碍),一些是明确的,比如使用_MemoryBarrier内在的。
在Lockless Programming Considerations for Xbox 360 and Microsoft Windows中深入讨论了内存模型的整个主题,这应该给你一个很好的概述。要注意:你正在进入的主题充满了难题和令人讨厌的惊喜。
注意:依赖于volatile是不可移植的,但如果您使用的是旧的C / C ++标准,那么无论如何都没有可移植的解决方案,因此,如果需要,请准备好面对不同平台重新实现它的需要。编写可移植线程代码时,volatile is considered almost useless:
对于多线程编程,有两个关键问题,通常会错误地认为volatile不会被解决:
原子
内存一致性,即另一个线程看到的线程操作的顺序。