4

我正在查看一些源代码,我想知道以下是否是线程安全的?我听说过编译器或 CPU 指令/读取重新排序(它与分支预测有关吗?)并且下面的 Data->unsafe_variable 变量可以随时由另一个线程修改。

我的问题是:根据编译器/CPU 重新排序读/写的方式,下面的代码是否有可能允许两次获取 Data->unsafe_variable?(见第二个片段)

注意:我不担心第一次访问,只要不通过'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() 而不是 volatile 演员表会更安全吗?(VS编译器)

编辑:为代码提供更多上下文。

4

3 回答 3

4

代码被破坏的原因有很多。我将只指出一个更微妙的,因为其他人已经指出了更明显的。对象不是volatile。将指针转换为指向 volatile 对象的指针不会使对象变得 volatile,它只是对编译器有欺骗性。

但还有更重要的一点——你正在以完全错误的方式处理这件事。您应该检查代码是否正确,即是否保证可以工作。你不够聪明,没有人能想出系统可能无法按照你的假设去做的所有可能方式。因此,请不要做出这些假设。

考虑诸如 CPU 读取重新排序之类的事情是完全错误的。您应该期望 CPU 执行并且只执行它需要执行的操作。你绝对不应该考虑它可能失败的具体机制,而只考虑它是否保证工作。

你所做的就像试图通过检查员工是否接种流感疫苗、检查他是否还活着等等来确定员工是否可以保证上班。您无法检查,甚至无法想到他可能无法出现的所有可能方式。所以如果发现你必须检查那些东西,那就不能保证,依赖它是坏的。时期。

你不能通过说“CPU 没有做任何可以破坏它的事情,所以没关系”来编写可靠的代码。您可以通过说“我确保我的代码不依赖于相关标准无法保证的任何内容”来制作可靠的代码。

为您提供了完成这项工作所需的所有工具,包括内存屏障、原子操作、互斥体等。请使用它们。

你不够聪明,无法想出不能保证有效的事情可能会失败的所有方式。而且你有很多东西可以保证工作。修复此代码,如果可能,与编写它的人讨论如何使用正确的同步。

这听起来有点啰嗦,对此我深表歉意。但是我见过太多使用这种“技巧”的代码,这些“技巧”在测试机器上运行良好,但在新 CPU、新编译器或新版本的操作系统出现时就崩溃了。像这样修复代码可能会令人难以置信的痛苦,因为这些黑客隐藏了实际的同步要求。正确的答案几乎总是清晰而准确地编写你真正想要的代码,而不是假设你会得到它,因为你不知道你不会得到它的任何原因。

这是来自痛苦经历的宝贵建议。

于 2012-06-08T08:35:53.343 回答
1

标准很明确。如果任何线程可能正在修改对象,则所有线程中的所有访问都必须同步,否则您将有未定义的行为。

于 2012-06-08T07:36:39.913 回答
0

C++ 的唯一可移植解决方案是C++11 atomics,它在即将推出的 VS 2012中可用。

至于 C,我不知道最近的 C 标准是否带来了一些可移植的设施,我没有遵循,但由于您使用的是 Visal Studio,无论如何都没关系,因为微软没有实施最近的 C 标准。

不过,如果您知道自己正在为 Visual Studio 开发,则可以依赖此编译器提供的保证,该保证适用于 C 和 C++。其中一些是隐式的(访问 volatile 变量也意味着应用了一些内存屏障),一些是显式的,例如使用_MemoryBarrier内在。

内存模型的整个主题在Xbox 360 和 Microsoft Windows 的无锁编程注意事项中进行了深入讨论,这应该会给您一个很好的概述。请注意:您输入的主题充满了棘手的主题和令人讨厌的惊喜。

注意:依赖 volatile 是不可移植的,但是如果您使用旧的 C / C++ 标准,无论如何都没有可移植的解决方案,因此如果需要,请准备好面对针对不同平台重新实现它的需求。在编写可移植线程代码时,volatile 被认为几乎没用

对于多线程编程,volatile 经常被错误地认为要解决两个关键问题:

  • 原子性

  • 内存一致性,即另一个线程看到的线程操作的顺序。

于 2012-06-08T08:17:20.710 回答