让我有这个循环:
static a;
for (static int i=0; i<10; i++)
{
a++;
///// point A
}
到这个循环2个线程进入......
我不确定某事......如果thread1进入POINT A会发生什么,保持在那里,而THREAD2进入循环10次,但是在将第10个循环增加到10之后,在检查i的值之前,如果它小于10, Thread1正在退出循环并假设增加i并再次进入循环。 Thread1将增加的价值是什么(我将会看到)?它会是10还是0?
是否可以认为Thread1会将i递增为1,然后线程2将再次循环9次(并且它们可能是8,7等等)
感谢
答案 0 :(得分:5)
如果多个线程之间共享i
,则所有投注均已关闭。在另一个线程的执行过程中,任何线程都可以在任何点上递增i
(包括该线程的递增操作的中途)。在上面的代码中没有任何有意义的方法来推理i
的内容。不要那样做。要么为每个线程提供自己的i
副本,要么增加和比较10个单个原子操作。
答案 1 :(得分:5)
你必须意识到增量操作实际上是真的:
read the value
add 1
write the value back
你必须问自己,如果其中两个同时发生在两个独立的线程中会发生什么:
static int a = 0;
thread 1 reads a (0)
adds 1 (value is 1)
thread 2 reads a (0)
adds 1 (value is 1)
thread 1 writes (1)
thread 2 writes (1)
对于两个同时增量,您可以看到其中一个可能会丢失,因为两个线程都读取了预先递增的值。
您提供的示例因静态循环索引而变得复杂,我最初没有注意到。 由于这是c ++代码,标准实现是所有线程都可以看到静态变量,因此所有线程只有一个循环计数变量。理所当然的事情是使用普通的自动变量,因为每个线程都有自己的,不需要锁定。
这意味着虽然有时你会失去增量,但你也可能获得它们,因为循环本身可能会丢失计数并重复多次。总而言之,这是不该做的一个很好的例子。
答案 2 :(得分:3)
这不是一个微妙的问题,因为如果同步成为问题,你绝不会在实际代码中允许这样做。
答案 3 :(得分:2)
我将在你的循环中使用i++
:
for (static int i=0; i<10; i++)
{
}
因为它模仿a
。 (注意,static
这里很奇怪)
考虑线程A是否在到达i++
时被暂停。线程B一直获得i
到9,进入i++
并使其成为10.如果它继续前进,则循环将存在。啊,但现在线程A恢复了!所以它从中断的地方继续:增加i
!所以i
变成11,你的循环被塞进去了。
线程共享数据时,需要对其进行保护。如果您的平台支持,您还可以使i++
和i < 10
以原子方式(永不被中断)发生。
答案 4 :(得分:1)
您应该使用mutual exclusion来解决此问题。
答案 5 :(得分:1)
这就是为什么在多线程环境中,我们假设使用锁。
在你的情况下,你应该写:
bool test_increment(int& i)
{
lock()
++i;
bool result = i < 10;
unlock();
return result;
}
static a;
for(static int i = -1 ; test_increment(i) ; )
{
++a;
// Point A
}
现在问题消失了。请注意lock()
和unlock()
应该锁定和解锁所有尝试访问i的线程共有的互斥锁!
答案 6 :(得分:0)
是的,任何一个线程都可以完成该循环中的大部分工作。但正如Dynite解释的那样,这将(并且应该)永远不会出现在实际代码中。如果同步是一个问题,您应该提供互斥(Boost,pthread或Windows线程)互斥,以防止此类竞争条件。
答案 7 :(得分:0)
为什么要使用静态循环计数器?
这有点像家庭作业,而且很糟糕。
答案 8 :(得分:0)
两个线程都有自己的i副本,因此行为可以是任何东西。这就是为什么会出现这样一个问题。
当您使用互斥锁或临界区时,线程通常会同步,但如果变量不是易失性的话,即使这样也不是绝对保证。
毫无疑问,有人会指出“volatile在多线程中毫无用处!”但人们说很多蠢事。你不必有波动,但它对某些事情有帮助。
答案 9 :(得分:0)
如果你的“int”不是原子机器字大小(想想64位地址+模拟32位VM的数据),你将"word-tear"
。在这种情况下,你的“int”是32位,但是机器以原子方式寻址64。现在你必须读取全部64,增加一半,并将它们全部写回。
这是一个更大的问题;如果你真的想要血淋淋的细节,可以在处理器指令集上使用gret gcc,以及如何在任何地方实现“volatile”。
添加“volatile”并查看机器代码的更改方式。如果您不是低头看芯片寄存器,请使用boost库并完成它。
答案 10 :(得分:0)
如果需要同时增加多个线程的值,则查找“原子操作”。对于linux,查找“gcc atomic operations”。大多数平台都有硬件支持,可以进行原子递增,添加,比较和交换等。对于这个原因,锁定会过于强大......原子公司比锁定公司解锁更快。如果您必须同时更改大量字段,则可能需要锁定,尽管您可以使用大多数原子操作一次更改128位字段。
volatile与原子操作不同。 Volatile帮助编译器知道何时使用变量副本是个坏主意。在其使用中,当您有多个线程更改数据时,volatile非常重要,您希望在没有锁定的情况下读取“最新版本”。 Volatile仍然无法修复你的a ++问题,因为两个线程可以同时读取“a”的值,然后两个都增加相同的“a”,然后最后一个写“a”获胜并且你丢失了一个inc。易失性会降低优化代码的速度,因为不要让编译器在寄存器中保存值而不是。