【问题标题】:Do I need to use locking with integers in c++ threads我是否需要在 c++ 线程中使用整数锁定
【发布时间】:2011-02-23 03:33:59
【问题描述】:

如果我在多个线程中访问单个整数类型(例如 long、int、bool 等),是否需要使用互斥锁等同步机制来锁定它们。我的理解是,作为原子类型,我不需要锁定对单个线程的访问,但我看到很多代码确实使用了锁定。分析此类代码表明使用锁会对性能造成重大影响,所以我宁愿不这样做。因此,如果我正在访问的项目对应于总线宽度整数(例如 32 位处理器上的 4 个字节),我是否需要在跨多个线程使用它时锁定对它的访问?换句话说,如果线程 A 正在写入整数变量 X,而线程 B 正在从同一个变量中读取,那么线程 B 是否有可能最终将前一个值的几个字节与几个字节混合在一起?写入的值?这种架构是否依赖,例如32 位系统上的 4 字节整数可以,但 64 位系统上的 8 字节整数不安全?

编辑:刚刚看到这个related post,这很有帮助。

【问题讨论】:

    标签: c++ multithreading atomic


    【解决方案1】:

    C++ 中不支持原子变量,因此您确实需要锁定。如果没有锁定,您只能推测将使用哪些确切指令进行数据操作,以及这些指令是否会保证原子访问——这不是您开发可靠软件的方式。

    【讨论】:

    • 我不得不(有点)不同意。 c++ 中没有原子变量,没有,但也没有任何锁。一旦进入多线程,您就必须依赖特定编译器提供的保证。 that 确实对原子性有很多保证。通常,对字大小对象的访问将是原子的。当然,C++ 语言不能保证这一点,但您的特定编译器可能会。当然,编译器通常很少保证重新排序,因此您可能仍然需要锁定,或者至少需要内存屏障,具体取决于您正在做什么
    【解决方案2】:

    是的,最好使用同步。多线程访问的任何数据都必须同步。

    如果是windows平台也可以在这里查看:Interlocked Variable Access

    【讨论】:

      【解决方案3】:

      是的。如果您使用的是 Windows,则可以查看 Interlocked 函数/变量,如果您支持 Boost,则可以查看他们的 implementation of atomic variables

      如果 boost 太重,把“atomic c++”放到你最喜欢的搜索引擎中会让你有很多思考的地方。

      【讨论】:

        【解决方案4】:

        你永远不会锁定一个值 - 你是在一个值上锁定一个操作。

        C 和 C++ 没有明确提及线程或原子操作 - 因此看起来它们可以或应该是原子的操作 - 语言规范不保证是原子的。

        无可否认,它会是一个非常不正常的编译器,它管理对 int 的非原子读取:如果您有一个读取值的操作 - 可能不需要保护它。但是,如果它跨越机器字边界,它可能是非原子的。

        m_counter++ 这样简单的操作涉及获取、递增和存储操作 - 竞争条件:另一个线程可以在获取之后但在存储之前更改值 - 因此需要由互斥锁保护 - 或查找您的编译器支持互锁操作。 MSVC 具有像 _InterlockedIncrement() 这样的函数,只要所有其他写入类似地使用互锁 api 来更新内存位置,它就会安全地增加内存位置 - 这比调用甚至是关键部分更轻量级。

        GCC 具有像 __sync_add_and_fetch 这样的内在函数,它也可用于对机器字值执行互锁操作。

        【讨论】:

        • 谢谢,InterlockedExchange 可能是我正在寻找的函数,因为只有一个线程实际写入相关变量,而其他线程只是读取它。
        【解决方案5】:

        在 99.99% 的情况下,您必须锁定,即使它可以访问看似原子的变量。由于 C++ 编译器不了解语言级别的多线程,因此它可以进行很多重要的重新排序。

        举个例子:我被一个自旋锁实现所困扰,其中解锁只是将零分配给volatile 整数变量。不出所料,编译器在锁下的实际操作之前重新排序了解锁操作,这导致了神秘的崩溃。

        见:

        1. Lock-Free Code: A False Sense of Security
        2. Threads Cannot be Implemented as a Library

        【讨论】:

        • 编译器BUG。 volatile 是优化边界。
        • @Joshua,我觉得有些讽刺吗?如果不是,那么不是,这不是编译器错误。没有什么能阻止编译器重新排序非易失性红/写围绕易失性的,它只能重新排序它们之间的易失性读/写。看到这个问题:stackoverflow.com/questions/2535148/…
        【解决方案6】:

        如果您在一台具有多个内核的机器上,您需要正确地执行操作,即使写入整数是原子的。问题有两个方面:

        1. 您需要阻止编译器优化实际写入! (这有点重要。;-))
        2. 您需要内存屏障(不是在 C 中建模的东西)以确保其他内核注意到您已更改的事实。否则你会被所有处理器之间的缓存和其他类似的脏细节纠缠在一起。

        如果只是第一件事,你可以标记变量 volatile,但第二件事确实是杀手,你只会真的在多核机器上看到差异.这恰好是一种比过去更加普遍的架构……哎呀!是时候停止马虎了;为您的平台使用正确的互斥锁(或同步或其他)代码,以及如何使内存按照您认为的方式工作的所有细节。

        【讨论】:

          【解决方案7】:

          多线程既困难又复杂。可能出现的难以诊断的问题数量非常多。特别是,在英特尔架构上,从对齐的 32 位整数读取和写入保证在处理器中是原子的,但这并不意味着在多线程环境中这样做是安全的。

          如果没有适当的保护,编译器和/或处理器可以重新排序代码块中的指令。它可以将变量缓存在寄存器中,并且它们在其他线程中是不可见的......

          锁定很昂贵,并且有不同的无锁数据结构实现来优化高性能,但很难正确地做到这一点。问题是并发错误通常是晦涩难懂且难以调试的。

          【讨论】:

            猜你喜欢
            • 2015-08-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-08-16
            • 2013-12-07
            相关资源
            最近更新 更多