关于多线程:可以让多个线程将相同的值写入相同的变量吗?

关于多线程:可以让多个线程将相同的值写入相同的变量吗?

Is it ok to have multiple threads writing the same values to the same variables?

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

GlobalVar.property = 11;

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


当您读回该状态并对其进行处理时,就会出现问题。编写是一条红鲱鱼-的确,只要这是一个单词,大多数环境就可以保证写操作是原子的,但这并不意味着包含该片段的较大代码段是线程安全的。首先,假设您的全局变量以不同的值开头-否则,如果您知道它始终相同,为什么它是变量?其次,想必您最终会再次读取此值?

问题是,大概是由于某种原因,您正在写入此共享状态位-表示发生了什么事吗?这就是它的缺点:当您没有锁定结构时,根本没有隐含的内存访问顺序。很难指出这里出了什么问题,因为您的示例实际上并不包含使用变量,因此,这是一个中立的类似于C的语法的琐碎示例:

1
2
3
4
5
6
7
8
9
10
11
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上,即使编译器不对事物进行重新排序,内存写入也可能在独立处理器的缓存之间重新排序。

如果这似乎无法为您解决,请在问题中提供示例的更多详细信息。如果不使用该变量,就不可能确切地说出这种用法是否安全。


这取决于该语句实际完成的工作。在某些情况下,还是会发生不良情况-例如,如果C ++类重载了=运算符,并且在该语句中执行了一些重要的操作。

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

为什么不在使用该变量时将其锁定在该变量周围?实际上,如果您以某种方式"知道"这是可能在代码中的某个时刻出现的唯一写语句,为什么不直接使用值11而不是将其写入共享变量呢?
(编辑:我想最好在代码中直接使用常量名称而不是魔术数字11。)

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


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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就可以了,并且您可以等待所有多线程操作完成后再读取该标志。我可能是错的。


通常,除非您的系统提供了原子操作(保证在单个周期内执行的操作),否则这不是安全的事情。
原因是,尽管" C"语句看起来很简单,但是经常会发生许多底层的汇编操作。

根据您的操作系统,您可以执行以下操作:

  • 采取互斥信号灯(mutex)以保护访问
  • 在某些操作系统中,您可以暂时禁用抢占,以确保您的线程不会掉出。
  • 某些操作系统提供的书写器或读取器信号量比普通的旧互斥量更具性能。

我希望结果不因为它会因编译器而异,语言会因语言而异,而OS会因OS而异。因此,不安全

但是,为什么要执行此操作-在一行中添加一条以获得互斥锁的功能只是一两行代码(在大多数语言中),这样就可以消除出现问题的可能性。如果这将是两次昂贵,那么您需要找到解决问题的另一种方法


假设该属性永远不会被分配给11以外的任何东西,那么我首先看不到有归属的原因。只需使其恒定即可。

只有在您打算更改该值时,才有意义分配,除非分配行为本身具有其他副作用-就像volatile写操作在Java中具有内存可见性的副作用一样。而且,如果您更改了多个线程之间共享的状态,则需要同步或"处理"并发问题。

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

编译器,JIT,CPU缓存。他们都在试图使您的代码尽可能快地运行,并且如果您对内存可见性没有任何明确的要求,那么他们将利用这一优势。如果不在您的计算机上,则其他人。


如果该操作是原子操作,则应该可以顺利进行。但是我不会在实践中这样做。最好仅获得对象的锁并写入值。


推荐阅读