7

我了解竞争条件以及多个线程如何访问同一个变量,一个人所做的更新可以被其他人忽略和覆盖,但是如果每个线程都将相同的值(不是不同的值)写入同一个变量怎么办?甚至这会导致问题吗?这段代码可以:

GlobalVar.property = 11;

(假设该属性永远不会被分配除 11 以外的任何值),如果多个线程同时执行它会导致问题吗?

4

7 回答 7

9

当您读回该状态并对其进行处理时,问题就来了。写入是一条红鲱鱼 - 确实,只要这是一个单词,大多数环境都保证写入将是原子的,但这并不意味着包含此片段的更大代码段是线程安全的。首先,大概你的全局变量包含一个不同的值开始 - 否则如果你知道它总是相同的,为什么它是一个变量?其次,大概你最终会再次读回这个值?

问题在于,您可能出于某种原因写入这一共享状态 - 表示发生了某些事情?这就是它失败的地方:当您没有锁定结构时,根本就没有隐含的内存访问顺序。很难指出这里有什么问题,因为您的示例实际上不包含变量的使用,所以这是一个中性 C 类语法的简单示例:

int x = 0, y = 0;

//thread A does:
x = 1;
y = 2;
if (y == 2)
    print(x);

//thread B does, at the same time:
if (y == 2)
    print(x);

线程 A 将始终打印 1,但线程 B 打印 0 是完全有效的。线程 A 中的操作顺序只需要从线程 A 中执行的代码中可观察到 - 允许线程 B 看到状态的任何组合。对 x 和 y 的写入实际上可能不是按顺序发生的。

即使在单处理器系统上也可能发生这种情况,大多数人并不期望这种重新排序 - 您的编译器可能会为您重新排序。在 SMP 上,即使编译器不重新排序,内存写入也可能在单独处理器的缓存之间重新排序。

如果这似乎没有为您解答,请在问题中包含您示例的更多详细信息。如果不使用该变量,就不可能明确地说这种用法是否安全。

于 2008-09-16T14:01:06.550 回答
3

这取决于该语句实际完成的工作。在某些情况下仍然会发生“不好的事情”——例如,如果 C++ 类重载了 = 运算符,并且在该语句中做了任何不平凡的事情。

我不小心编写了使用 POD 类型(内置原始类型)执行类似操作的代码,并且效果很好——但是,这绝对不是一个好习惯,而且我不确定它是否可靠。

为什么不在使用它时锁定这个变量周围的内存呢?事实上,如果你以某种方式“知道”这是唯一可能在代码中出现的写入语句,为什么不直接使用值 11,而不是将其写入共享变量?(编辑:我想最好在代码中直接使用常量名称而不是幻数11,顺便说一句。)

如果您使用它来确定至少一个线程何时到达此语句,您可以使用从 1 开始的信号量,并由第一个命中它的线程递减。

于 2008-09-16T13:34:19.703 回答
1

我希望结果是不确定的。因为它会因编译器而异,语言因语言而异,操作系统因操作系统而异。所以不,它不安全

你为什么要这样做 - 添加一行以获得互斥锁只是一两行代码(在大多数语言中),并且会消除任何问题的可能性。如果这将是两个昂贵​​的,那么您需要找到解决问题的替代方法

于 2008-09-16T13:27:42.170 回答
1

In General, this is not considered a safe thing to do unless your system provides for atomic operation (operations that are guaranteed to be executed in a single cycle). The reason is that while the "C" statement looks simple, often there are a number of underlying assembly operations taking place.

Depending on your OS, there are a few things you could do:

  • Take a mutual exclusion semaphore (mutex) to protect access
  • in some OS, you can temporarily disable preemption, which guarantees your thread will not swap out.
  • Some OS provide a writer or reader semaphore which is more performant than a plain old mutex.
于 2008-09-16T13:43:43.643 回答
1

这是我对这个问题的看法。

您有两个或多个线程正在运行写入变量……例如状态标志或其他东西,您只想知道其中一个或多个是否为真。然后在代码的另一部分(线程完成后),您要检查并查看是否至少在线程上设置了该状态...例如

bool flag = false
threadContainer tc
threadInputs inputs

check(input)
{
    ...do stuff to input
    if(success)
        flag = true
}

start multiple threads
foreach(i in inputs) 
   t = startthread(check, i)
   tc.add(t)  // Keep track of all the threads started

foreach(t in tc)
    t.join( )  // Wait until each thread is done

if(flag)
   print "One of the threads were successful"
else
   print "None of the threads were successful"

我相信上面的代码是可以的,假设你不知道哪个线程将状态设置为 true,并且你可以等待所有多线程的东西完成,然后再读取该标志。不过我可能是错的。

于 2008-09-21T17:24:30.707 回答
-1

假设该属性永远不会被分配除 11 以外的任何东西,那么我首先看不到分配的理由。那就让它成为一个常数。

分配仅在您打算更改值时才有意义,除非分配行为本身具有其他副作用 - 例如 volatile 写入在 Java 中具有内存可见性副作用。如果您更改多个线程之间共享的状态,那么您需要同步或以其他方式“处理”并发问题。

当您在没有适当同步的情况下为多个线程之间共享的某个状态分配一个值时,则无法保证其他线程何时会看到该更改。并且没有可见性保证意味着其他线程可能永远不会看到分配。

编译器、JIT、CPU 缓存。他们都试图让你的代码尽可能快地运行,如果你没有对内存可见性提出任何明确的要求,那么他们就会利用这一点。如果不在您的机器上,那么其他人。

于 2008-09-21T13:28:16.220 回答
-1

If the operation is atomic, you should be able to get by just fine. But I wouldn't do that in practice. It is better just to acquire a lock on the object and write the value.

于 2008-09-16T13:44:37.870 回答