如果我在多个线程中访问单个整数类型(例如long,int,bool等等),我是否需要使用同步机制(如互斥锁)来锁定它们。我的理解是,作为原子类型,我不需要锁定对单个线程的访问,但我看到很多代码确实使用了锁定。对这些代码进行概要分析表明使用锁具有显着的性能损失,所以我宁愿不这样做。因此,如果我访问的项目对应于总线宽度整数(例如32位处理器上的4个字节),我是否需要在跨多个线程使用时锁定对它的访问?换句话说,如果线程A在从同一个变量读取的同时写入整数变量X,那么线程B是否有可能以前几个字节的几个字节结束前一个值的几个字节。正在写的价值?这种架构是否依赖于,例如确定32位系统上的4字节整数但64位系统上8字节整数不安全吗?
修改:刚看到这个related post,这有点帮助。
答案 0 :(得分:7)
您永远不会锁定值 - 您将操作锁定在某个值上。
C& C& C ++没有明确提到线程或原子操作 - 所以看起来它们可能或应该是原子的操作 - 不能保证语言规范是原子的。无可否认,它是一个非常不正常的编译器,它管理一个int上的非原子读取:如果你有一个读取值的操作 - 可能没有必要保护它。但是,如果它跨越机器字边界,它可能是非原子的。
像m_counter++
这样简单的操作涉及获取,增量和存储操作 - 竞争条件:另一个线程可以在获取之后但在存储之前更改值 - 因此需要通过互斥锁保护 - 或者找到您的编译器支持互锁操作。 MSVC具有像_InterlockedIncrement()这样的函数,只要所有其他写操作类似地使用互锁apis来更新内存位置,它就会安全地增加内存位置 - 这比调用一个临界区更轻量级。
GCC具有内部函数,如__sync_add_and_fetch
,也可用于对机器字值执行互锁操作。
答案 1 :(得分:4)
C ++中不支持原子变量,所以你需要锁定。如果没有锁定,您只能推测用于数据操作的确切指令以及这些指令是否能保证原子访问 - 这不是您开发可靠软件的方式。
答案 2 :(得分:3)
是的,最好使用同步。必须同步多个线程访问的任何数据。
如果是Windows平台,您也可以在此处查看:Interlocked Variable Access。
答案 3 :(得分:3)
在99.99%的情况下,必须锁定,即使它可以访问看似原子的变量。由于C ++编译器不知道语言级别的多线程,它可以做很多非平凡的重新排序。
一个例子:我被一个自旋锁实现所咬,其中unlock只是为volatile
整数变量赋值为零。编译器在锁定之前的实际操作之前重新排序解锁操作,不出所料,导致神秘的崩溃。
请参阅:
答案 4 :(得分:3)
如果您使用的是具有多个核心的计算机,则需要才能正确执行操作,即使整数写入是原子的。问题有两个:
如果只是第一件事,你可以标记变量volatile
,但第二个真的是杀手,你只会真的看到差异多核机器。这恰好是一个比以前更常见的架构...哎呀!是时候停止草率了;为您的平台使用正确的互斥锁(或同步或其他)代码,以及如何使内存工作的所有细节都会像您认为的那样消失。
答案 5 :(得分:2)
是。如果你在Windows上,你可以查看Interlocked函数/变量,如果你是Boost说服那么你可以查看他们的implementation of atomic variables。
如果提升太重,将“atomic c++”放入你最喜欢的搜索引擎会给你足够的思考。
答案 6 :(得分:2)
多线程是艰难而复杂的。可以解决的难以诊断问题的数量非常多。特别是,在intel体系结构中,对齐的32位整数的读取和写入在处理器中保证是原子的,但这并不意味着在多线程环境中这样做是安全的。
如果没有适当的防护,编译器和/或处理器可以重新排序代码块中的指令。它可以在寄存器中缓存变量,它们在其他线程中不可见......
锁定是昂贵的,并且存在不同的无锁数据结构实现以优化高性能,但很难正确执行。问题是并发错误通常很模糊,难以调试。