假设我们有这些全局变量:
int x = 0;
int y = 0;
spinlock_t* lock;
假设我们同时运行这两个线程。
void thread1(void)
{
y++;
spin_lock(lock);
x++;
spin_unlock(lock):
}
void thread2(void)
{
spin_lock(lock);
x++;
spin_unlock(lock);
y++;
}
我的结果为y=1
和x=2
,但我希望x和y都等于2.为什么会出现这种情况?
答案 0 :(得分:0)
考虑以下执行顺序:
x
读取到寄存器A x
,现在包含1
y
读取到寄存器A y
读取到注册表B y
,现在包含1
y
,现在包含1
x
读取到注册表A x
,现在包含2
由于没有锁定保护对y
的访问权限,步骤6到11中混合的操作序列导致y
仅增加1
而不是2
。这就是当您有多个线程修改非原子变量时需要锁定的原因。
在一个线程中的锁定区域之前和另一个线程中的锁定区域之后递增y
不能确保互斥,因为您无法确定线程1是否会尝试在线程2之前运行。
答案 1 :(得分:0)
您没有指定您正在使用的线程模型或标准。但是,如果您访问一个线程中的对象而另一个线程正在或可能正在修改它,则典型标准会指定它的未定义(或未指定)行为。
你明确地使用y
做到了这一点。因此,你没有理由期待任何特定的结果 - 任何事情都可能发生。有了一些线程标准,它可能会崩溃或产生任何结果。与其他人一起,y
的最终值未指定,但代码保证可以按预期执行。
不要陷入关于特定硬件(例如高速缓存和寄存器)可能或可能做什么的推理。这与问题的答案完全无关,因为它适用于您编写遵循规范或标准的代码的任何问题。您无法通过查找某些特定硬件上的任何失败方式来证明代码是安全的。
更重要的是,您无法通过找到某种特定硬件或平台上的失败方式来证明代码不安全。 (这就是为什么Barnar的答案是错误的。)我们总能想象代码可以执行的方式使它失败 - 它是编译器而不是编译代码的工作方式,除非代码本身出现问题。
例如,请考虑以下代码:
int i;
void foo()
{
i++;
bar();
i++;
}
void bar()
{
i++;
}
认为foo
可能只会增加i
两次是完全无效的,因为i
的值可能会在bar
的调用中保存在寄存器中,将bar
的更改丢失为i
。为什么?因为编译器只允许在寄存器中保存i
,因为它可以证明这不会影响代码的输出,因为代码遵循所有标准规则。
关于事情可能发生的原因,如果代码违反了某些规则,代码失败的原因只能起作用。否则,编译器的工作就是使代码不会失败,无论如何。
Barmar的论证同样适用于上面的代码,但很明显代码不会失败。所以这个论点是无效的。代码遵循规则或不遵守规则。如果它不遵守规则,它就会被破坏,因为它不遵守规则。如果它遵循规则,则它是编译器和平台的工作,以确保它不会以某种方式失败。
据推测,代码被破坏是因为它没有遵循线程平台或其编码标准的规则。但我们无法确定,因为您还没有告诉我们该平台或标准是什么。