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

时间:2020-03-05 18:55:54  来源:igfitidea点击:

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

GlobalVar.property = 11;

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

解决方案

回答

我希望结果不确定。因为在不同的编译器中,它会因语言而异,在语言与操作系统之间会有所不同,因此,这是不安全的

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

回答

这取决于该语句实际完成的工作。例如,在某些情况下,如果C ++类重载了=运算符,并且在该语句中执行了一些不平凡的事情,那么在某些情况下仍然会发生某些问题。

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

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

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

回答

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

根据操作系统,我们可以执行以下操作:

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

回答

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

回答

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

问题是,大概是由于某种原因我们正在写入共享状态位,以表明发生了某些事情?这就是它的缺点:当我们没有锁定结构时,根本没有隐含的内存访问顺序。很难指出这里出了什么问题,因为示例实际上并未包含使用变量,因此,这是一个中立的类似于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上,即使编译器不对事物进行重新排序,内存写入也可能在独立处理器的缓存之间重新排序。

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

回答

假设该属性永远不会被分配为11以外的任何东西,那么我首先看不到分配的原因。然后将其设为常数即可。

只有当我们打算更改该值时,才有意义分配,除非分配行为本身具有其他副作用,例如易失性写入在Java中具有内存可见性副作用。而且,如果我们更改了多个线程之间共享的状态,则需要同步或者以其他方式"处理"并发问题。

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

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

回答

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

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

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就可以了,并且可以在读取该标志之前等待所有多线程操作完成。我可能是错的。