多个线程访问一个变量

时间:2015-07-10 23:02:57

标签: c multithreading concurrentmodification

我在正在阅读的教科书中发现了这个问题。解决方案也在下面给出。我无法理解最小值是多少2.为什么线程不能读取0,所有其他线程都执行并写入1?无论是1还是2,最后写的线程还必须完成自己的循环?

int n = 0;
int main(int argc, char **argv) {
 for (i = 0; i < 5; i++) {
 int tmp = n;
 tmp = tmp + 1;
 n = tmp;
 }
 return 0;
}
  

如果单个线程运行此应用程序,您将期望最终   输出为5.如果5个线程并行运行相同的循环怎么办?什么   n可能具有的最大和最小值?应该是最大的   selfevident:25,从5个线程增加5个。然而,   关于最小可能值的推理更加困难。提示:n   可以小于5,但由你决定原因。

解决方案:

  

有五个线程运行这个五次迭代循环而没有   防止并发访问,n可以达到的最低值   是两个。在工作时,了解如何达到此结果是最简单的   从最终结果倒退。最终输出为2,a   线程必须从n读取值1,然后递增它   写了两个。这意味着另一个线程写了一个,暗示   它最初也读为零(这也是n的起始值)。   这解释了五个线程中的两个线程的行为。然而,   要发生此行为,其他三个线程的结果必须   被覆盖了。两个有效的执行可以实现这一点。   要么1)所有三个线程都开始并完成了之间的执行   第一个线程读零并写一个,或2)所有三个线程   开始并完成最终线程读取和之间的执行   写两个。两个执行顺序都有效。

4 个答案:

答案 0 :(得分:4)

假设每个线程都有一个本地i(即每个线程无论如何都会运行5次迭代),让我们试着得到1作为结果。这意味着写入值的 last 线程必须在其 5th 迭代中为n读取0。唯一可能发生这种情况的方法是,如果在该线程的第5次迭代开始时还没有线程写入n,那么该线程必须在其第5次迭代中,该线程本身必须写入{ {1}},因此不可能。

因此,最小可能的结果是2,这可能发生,例如,如下所示:写入n的最后一个线程已完成4次迭代,然后另一个线程写入1,最后一个线程在开始时读取1在第5次迭代中,所有其他线程在最后一个线程之前完成所有迭代,最后最后一个线程完成第5次迭代,写入2。

免责声明:我正在回答有关多线程的概念问题 - 正如其他人所指出的那样,如果C代码缺乏原子性可能会导致未定义的行为和任意结果提出按原样使用。基于问题的“不言而喻”的最大数字案例,我猜测教科书的作者要么没有意识到这一点,要么正在使用类似C的伪代码来说明概念。如果是前者,那么正确的答案就是这本书是错的,但我认为后一种情况下的答案也具有教育意义。

答案 1 :(得分:2)

添加一些见解:使用+运算符在C中添加,减去等不仅仅是1个操作。在汇编级别向下,+操作由多个指令组成。如果多个线程要访问一个变量并且这些指令存在错误的交错,则最终结果可能是非常不正确的结果 - &gt;这是我们需要互斥,信号量和条件变量之类的另一个原因。

答案 2 :(得分:2)

  

最大的应该是自愿的:25,从5个线程中增加5个。

完全错误。无论如何说这都不应该被听(至少涉及线程的事情),期间。

 int tmp = n;
 tmp = tmp + 1;
 n = tmp;

想象一下,CPU没有增量操作,但具有高效的“add 10”操作和高效的“减9”操作。在这样的CPU上,tmp = tmp + 1;可以优化为tmp += 10; tmp -= 9;。编译器还可以通过对tmp进行操作来完全优化n

所以这段代码可能等同于:

n += 10;
n -= 9;

现在假设发生这种情况:所有五个线程都添加10,所以n现在是50.第一个线程读取50,其他四个线程减去9.第一个线程从50读取和写入减去9 41.所以当一切都完成后,n就是41。

因此,声称不言而喻的是完全错误的。无论谁写的都不懂C中的线程。

  

如果每个线程写一个1,那么最终值不能神奇地成为其他东西

也完全是完全错误的。考虑一个CPU,通过先写0然后递增值来写1。如果这发生在两个核心上,最终结果可能是2.这本教科书是由一个从根本上不理解线程和未定义行为的人编写的。

(我假设这本教科书并不局限于某些特殊的语境,其中所说的是真的。例如,它可能使用“类C”代码作为平台中立汇编语言的一种形式,它可能会对对齐整数具有特定保证的平台做出假设。但如果是这样的话,它的教学内容并不能转换为C代码,而只适用于在其规则的CPU上编写汇编代码的人符合教科书的假设。)

答案 3 :(得分:0)

重点是线程正在共享相同的数据实例。此外,似乎假设所有其他线程以相同的执行速率运行。

因此,当每个线程围绕循环(到达i++的{​​{1}}部分)时,它们几乎同时递增for,因此就好像编写了代码:

i

至少在给出最小迭代次数的极端情况下。