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

CPU是否自动给内存赋值?

  •  9
  • Poni  · 技术社区  · 16 年前

    我一直在想一个很快的问题:CPU是自动分配值,还是逐位分配值(例如32位整数)。
    如果是逐位的,那么访问这个精确位置的另一个线程能否得到要分配值的“部分”?

    想想看:
    我有两个线程和一个共享的“unsigned int”变量(称之为“g_uival”)。
    两个线程循环。
    on正在用printf(“%u\n”,g_ival)打印“g_ival”。
    第二个就是增加这个数字。
    打印线程是否会打印完全不属于“g_ival”值或部分不属于“g_ival”值的内容?

    在代码中:

    unsigned int g_uiVal;
    
    void thread_writer()
    {
     g_uiVal++;
    }
    void thread_reader()
    {
     while(1)
      printf("%u\n", g_uiVal);
    }
    
    7 回复  |  直到 16 年前
        1
  •  5
  •   crazyscot    16 年前

    取决于CPU和内存的总线宽度。在PC环境中,除了真正古老的CPU之外,最多32位访问是原子访问;64位访问可能是也可能不是原子访问。在嵌入式空间中,很多(大多数?)CPU是32位宽的,没有任何更宽的配置,因此 int64_t 保证是非原子的。

        2
  •  3
  •   Evan Teran    16 年前

    我认为唯一正确的答案是“视情况而定”。你可能会问什么?

    对于CPU的初学者来说。但也有一些CPU是用于写入字宽值的原子CPU,但仅在对齐时。在C语言水平上,这并不是你能保证的。

    许多编译器提供“内部函数”来发出正确的原子操作。这些扩展的作用类似于函数,但是为目标体系结构发出正确的代码以获得所需的原子操作。例如: http://gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html

        3
  •  1
  •   myron-semack    16 年前

    你的问题是“一点一点”的。我不认为任何架构一次只做一点操作,除了一些专门的串行协议总线。标准内存读/写是用8、16、32或64位的粒度完成的。因此,您示例中的操作可能是原子操作。

    然而,答案严重依赖于平台。

    • 这取决于CPU的能力。 硬件能做原子32位吗 手术?这里有一个提示:如果 您正在处理的变量较大 大于本机寄存器大小(例如 32位系统上的64位int),它是 绝对不是原子的。
    • 这取决于编译器 生成机器代码。它可以 已将您的32位变量 访问4x 8位内存读取。
    • 如果地址 您正在访问的未对齐 越过机器的自然字 边界。你可以点击一个缓存 错误或页面错误。

    使用您发布的代码示例很可能会看到一个损坏的或意外的值。

    您的平台可能提供了一些执行原子操作的方法。如果是Windows平台,则通过 Interlocked functions . 对于Linux/Unix,请查看 atomic_t type .

        4
  •  0
  •   Michael Petrotta user3140870    16 年前

    除此之外,到目前为止,另一个潜在的问题是缓存。CPU倾向于使用本地(死后)内存缓存,这些缓存可能会或可能不会立即刷新回主内存。如果框中有多个CPU,则在修改CPU之后的一段时间内,另一个CPU可能不会看到这些更改-除非有某个同步命令通知所有CPU它们应该在死缓存上进行同步。正如您可以想象的那样,这种同步可以大大降低处理速度。

        5
  •  0
  •   Puppy    16 年前

    别忘了,在优化时编译器假设是单线程的,这整个过程可能就这样结束了。

        6
  •  0
  •   Chris Dodd    16 年前

    posix定义特殊类型 sig_atomic_t 这就保证了写入它的信号是原子的,从您希望的其他线程的角度来看,这也将使它成为原子的。它们并没有专门定义这样的原子跨线程类型,因为线程通信应该由互斥体或其他同步原语介导。

        7
  •  0
  •   Chris O    16 年前

    考虑到现代微处理器(忽略微控制器),32位分配是原子的,而不是逐位的。

    然而,现在完全脱离了你的问题主题…当然,由于本例中缺少同步,由于指令重新排序和多个核心(每个核心在其缓存中都有自己的g_ival副本),打印线程仍然可以打印一些不需要的内容。