【发布时间】:2014-07-29 03:06:10
【问题描述】:
我正在编写一个多线程应用程序,但在 SPARC 平台上遇到了问题。最终我的问题归结为这个平台的原子性以及我如何获得这个结果。
一些伪代码有助于澄清我的问题:
// Global variable
typdef struct pkd_struct{
uint16_t a;
uint16_t b;
} __attribute__(packed) pkd_struct_t;
pkd_struct_t shared;
Thread 1:
swap_value() {
pkd_struct_t prev = shared;
printf("%d%d\n", prev.a, prev.b);
...
}
Thread 2:
use_value() {
pkd_struct_t next;
next.a = 0; next.b = 0;
shared = next;
printf("%d%d\n", shared.a, shared.b);
...
}
线程 1 和 2 正在访问共享变量“shared”。一个是设置,另一个是获取。如果线程 2 将“共享”设置为零,我希望线程 1 在设置之前或之后读取计数——因为“共享”在 4 字节边界上对齐。但是,我偶尔会看到线程 1 读取 0xFFFFFF00 形式的值。即高位 24 位是 OLD,但低位字节是 NEW。看来我得到了一个中间值。
查看反汇编,use_value 函数只是执行“ST”指令。鉴于数据是对齐的并且没有跨越单词边界,对这种行为有什么解释吗?如果 ST 确实不是以这种方式使用的原子,这是否解释了我看到的结果(仅更改了 1 个字节?!?)? x86上没有问题。
更新 1: 我找到了问题,但没有找到原因。 GCC 似乎正在生成逐字节读取共享变量的程序集(因此允许获得部分更新)。添加了评论,但我对 SPARC 组件不太满意。 %i0 是指向共享变量的指针。
xxx+0xc: ldub [%i0], %g1 // ld unsigned byte g1 = [i0] -- 0 padded
xxx+0x10: ...
xxx+0x14: ldub [%i0 + 0x1], %g5 // ld unsigned byte g5 = [i0+1] -- 0 padded
xxx+0x18: sllx %g1, 0x18, %g1 // g1 = [i0+0] left shifted by 24
xxx+0x1c: ldub [%i0 + 0x2], %g4 // ld unsigned byte g4 = [i0+2] -- 0 padded
xxx+0x20: sllx %g5, 0x10, %g5 // g5 = [i0+1] left shifted by 16
xxx+0x24: or %g5, %g1, %g5 // g5 = g5 OR g1
xxx+0x28: sllx %g4, 0x8, %g4 // g4 = [i0+2] left shifted by 8
xxx+0x2c: or %g4, %g5, %g4 // g4 = g4 OR g5
xxx+0x30: ldub [%i0 + 0x3], %g1 // ld unsigned byte g1 = [i0+3] -- 0 padded
xxx+0x34: or %g1, %g4, %g1 // g1 = g4 OR g1
xxx+0x38: ...
xxx+0x3c: st %g1, [%fp + 0x7df] // store g1 on the stack
知道为什么 GCC 会生成这样的代码吗?
更新 2:向示例代码添加更多信息。 Appologies - 我正在使用新代码和遗留代码的混合,很难区分相关的内容。另外,我理解共享这样的变量通常是非常不鼓励的。但是,这实际上是在锁定实现中,更高级别的代码将使用它来提供原子性,并且使用 pthread 或特定于平台的锁定不是一个选项。
【问题讨论】:
-
你为什么要用
%lld格式化uint32_t?这可能转换的字节数是变量中的两倍。 -
请不要这样做。您拥有轻松编写保证工作的代码所需的工具。你为什么要搞乱你希望能以某种方式工作的代码?
-
@DavidSchwartz 这是一个锁实现。我知道这通常是非常不受欢迎的。我无法使用 pthread 锁、特定于架构的原子操作等典型工具。当前的实现依赖于对齐的共享数据和 st/ld 本身是原子的。
-
听起来你正在尝试不可能的事情。没有便携式方法可以做到这一点,而且您似乎已经排除了所有已知的非便携式方法。充其量,您将在 CPU、编译器和您碰巧编译它的选项的组合上“碰巧工作”的代码。
标签: multithreading assembly sparc