代码之家  ›  专栏  ›  技术社区  ›  SmacL

是否需要在C++线程中使用带锁的整数

  •  7
  • SmacL  · 技术社区  · 14 年前

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

    编辑: 刚刚看到这个 related post 这有一点帮助。

    7 回复  |  直到 12 年前
        1
  •  7
  •   Chris Becke    14 年前

    您永远不会锁定一个值-您正在锁定一个值上的操作。

    C++并没有明确地提到线程或原子操作-因此,看起来像或应该是原子的操作不是由语言规范保证为原子的。

    不可否认,它是一个非常离谱的编译器,在int上管理一个非原子的读取:如果您有一个读取值的操作,那么可能不需要保护它。但是-如果跨越机器词边界,它可能是非原子的。

    操作简单如 m_counter++ 涉及一个获取、增量和存储操作-一个争用条件:另一个线程可以在获取之后但在存储之前更改值-因此需要由互斥保护-或者找到编译器对互锁操作的支持。msvc具有类似于_interlocked increment()的函数,只要所有其他写操作都使用类似的互锁API来更新内存位置,该函数就可以安全地增加内存位置,这比调用甚至是关键的节更轻数量级。

    GCC具有如下固有功能: __sync_add_and_fetch 也可用于对机器字值执行联锁操作。

        2
  •  4
  •   sharptooth    14 年前

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

        3
  •  3
  •   Incognito    14 年前

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

    如果是Windows平台,您也可以在此处进行检查: Interlocked Variable Access .

        4
  •  3
  •   Donal Fellows    14 年前

    如果你在一台有多个核心的机器上,你 需要 即使整数的写入是原子的,也要做正确的事情。问题有两个方面:

    1. 您需要停止编译器优化实际的写入!(这有点重要。;(-))
    2. 您需要内存屏障(不是用C建模的东西),以确保其他核心注意到您已经改变了事情。否则,您将陷入所有处理器和其他类似的脏细节之间的缓存中。

    如果这只是第一件事,你就可以标记变量了。 volatile 但第二个是真正的杀手,你只会 真正地 看看多核机器的不同之处。这恰好是一种比过去更加普遍的架构。该停止草率行事了;为您的平台使用正确的互斥(或同步或其他)代码,以及如何让内存像您认为的那样工作的所有细节。

        5
  •  3
  •   Alex B    14 年前

    在99.99%的案例中,你 必须 锁,即使它可以访问看似原子的变量。由于C++编译器在语言层面上不知道多线程,所以它可以做很多非平凡的排序。

    举个例子:我被一个自旋锁实现咬了一口,在这个实现中,unlock只是将0分配给 volatile 整数变量。编译器在锁下的实际操作之前对解锁操作进行了重新排序,这无疑会导致神秘的崩溃。

    见:

    1. Lock-Free Code: A False Sense of Security
    2. Threads Cannot be Implemented as a Library
        6
  •  2
  •   David Rodríguez - dribeas    14 年前

    多线程是困难和复杂的。可能出现的难以诊断的问题数量相当大。特别是,在Intel体系结构中,从对齐的32位整数进行读写操作保证了处理器中的原子性,但这并不意味着在多线程环境中这样做是安全的。

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

    锁是昂贵的,并且有不同的无锁数据结构实现来优化高性能,但是很难正确执行。问题是并发性错误通常是模糊的,很难调试。

        7
  •  2
  •   graham.reeds    14 年前

    对。如果你在窗户上,你可以看看 Interlocked 函数/变量,如果你有助于说服,那么你可以看看它们 implementation of atomic variables .

    如果提速太重 atomic c++ “进入你最喜欢的搜索引擎,会给你很多思考的食物。