嘿伙计......所以我试图刷新我的C线程,我发现的问题是:
给定全局变量int x = 0;实现函数void useless(int n),它创建n个线程,在循环中将x增加1,每个线程在x达到100时终止。
我只是没有线程处理,需要一个坚实的例子来建立我的基础。这必须尽可能使用pthread系统调用。
答案 0 :(得分:3)
首先,你需要确定你想要实现的目标,以及不同线程的指令可以通过什么方式交织以防止发生这种情况。
C ++x
中的递增运算符通常实现为从存储器读取i的值到寄存器中;递增寄存器;将值写入内存:
r1 ← xglobal r1 ← r1 + 1 xglobal ← r1
因此x global 的值加1。
如果你有两个并行的线程,那么它们可以破坏性地交织
initial xglobal = 99 r1 ← xglobal compare r1 100 r2 ← xglobal compare r2 100 r1 ← r1 + 1 == 100 r2 ← r2 + 1 == 100 xglobal ← r1 == 100 xglobal ← r2 == 100 r1 ← xglobal compare r1 100 stop r2 ← xglobal compare r1 100 stop final xglobal = 100
所以x global 的值加1,尽管两个线程都递增它。
(我正在消除缓存的影响,这意味着线程2中的变量读取就像在线程1中写入之前一样,即使线程1的写入在读入之前发生也是如此挂钟时间。在pthreads中获取和释放互斥锁会导致内存障碍,这会阻止所有读取操作,就好像它们发生在写入之后,就像它们在获取或释放之前发生一样。)
(上面相当于for ( int r; ( r = x ) < 100; x = r + 1 )
而不是for (; x < 100; x = x + 1 )
,它可能有额外的x读取,因此有另一个点,线程可以干扰)
类似地,一个线程的增量可以破坏另一个线程的增量,从而允许线程以i&lt; 100:
initial xglobal = 98 r2 ← xglobal r1 ← xglobal compare r1 100 r1 ← r1 + 1 == 99 xglobal ← r1 r1 ← xglobal compare r1 100 r1 ← r1 + 1 == 100 xglobal ← r1 r1 ← xglobal compare r1 100 stop compare r2 100 r2 ← r2 + 1 == 99 xglobal ← r2 ... final xglobal = 99
左边线程的第二个增量被第一个增量覆盖,它将以x&lt;的全局可见值终止。 100。
你可能知道这一切,并且可能想要使用一种机制来防范它。
我说“可能”因为您的要求不明确 - 当x达到100时,上面的线程终止;要求并没有说它没有说那里。
因此,由于在没有编写x global ←100的情况下没有线程终止,实际上可以在没有任何锁定的情况下满足该要求,但x
可以递增n
* 100次而不是100次。 (如果限制大于一个字节,则在某些平台上写入x可能是非原子的,如果来自不同线程的字节混合在一起,则可能导致无限循环,但是对于不会发生的限制为100 )
一种技术是使用mutex,当一个线程持有互斥锁上的锁时,阻止其他线程运行。如果在读取x global 之前获取锁定,并且在写入x global 之前未释放锁定,则线程的读取和写入不能交错。
initial xglobal = 98 lock (mutex) mutex locked lock(mutex) blocked r1 ← xglobal compare r1 100 r1 ← r1 + 1 == 99 xglobal ← r1 release ( mutex ) mutex locked r2 ← xglobal compare r2 100 r2 ← r2 + 1 == 100 xglobal ← r2 release ( mutex ) lock (mutex) mutex locked r1 ← xglobal compare r1 100 release ( mutex ) stop ... final xglobal = 100
在pthread之外,您可能希望使用平台的比较和交换操作(gcc中的__sync_val_compare_and_swap
),该操作将地址设置为旧值和新值,并以原子方式设置内存如果它等于旧值,则在新值的地址处。这使您可以将逻辑编写为:
for ( int v = 0; v < 100; ) {
int x_prev = __sync_val_compare_and_swap ( &x, v, v + 1 );
// if the CAS succeeds, the value of x has been set to is x_prev + 1
// otherwise, try again from current last value
if ( x_prev == v )
v = x_prev + 1;
else
v = x_prev;
}
所以,如果
initial xglobal = 98 initial v1 = 0 initial v2 = 0 cmp v1 100 x_prev1 ← CASV ( xglobal, v1, v1 + 1 ) = 98 ( set fails with x == 98 ) cmp v2 100 x_prev2 ← CASV ( xglobal, v1, v1 + 1 ) = 98 ( set fails with x == 98 ) v1 ← x_prev1 = 98 // x_prev != v v2 ← x_prev2 = 98 cmp v2 100 x_prev2 ← CASV ( xglobal, v1, v1 + 1 ) = 98 ( set succeeds with x == 99 ) v2 ← x_prev2 + 1 = 99 // as x_prev == v cmp v1 100 x_prev1 ← CASV ( xglobal, v1, v1 + 1 ) = 99 ( set fails with x == 99 ) v1 ← x_prev1 = 99 // as x_prev != v cmp v1 100 x_prev1 ← CASV ( xglobal, v1, v1 + 1 ) = 99 ( set succeeds with x == 100) v1 ← x_prev1 + 1 = 100 // as x_prev == v cmp v2 100 x_prev2 ← CASV ( xglobal, v1, v1 + 1 ) = 100 ( set fails with x == 100 ) v2 ← x_prev2 = 100 // as x_prev != v cmp v1 100 cmp v2 100 stop stop
在每个循环中,x global 将原子设置为r 1 + 1的值当且仅当其先前的值为[R <子> 1 子>;如果不是,则r 1 将被设置为CASV操作期间测试的x global 的值。这减少了在大多数实现中保持锁定的时间量(尽管它仍然需要在CAS操作期间锁定内存总线,只有那些操作将被序列化。因为在多核上执行CAS是昂贵的,它可能赢了对于这样一个简单的案例,要好得多。)
答案 1 :(得分:2)
您需要一个互斥锁来保护变量。 每个线程将锁定互斥锁,增加变量并释放互斥锁。 不执行此操作的每个线程都是流氓线程。
答案 2 :(得分:0)
您需要的是一个关键部分。在Windows下,这将是EnterCriticalSection,但在pthread环境中,等效的是pthread_mutex_lock
。有关指示,请参阅here。
答案 3 :(得分:-1)
如果X&gt; = 100,如果每个线程都可以退出,我认为InterlockedIncrement就足够了。
我永远不会使用关键部分,除非我真的必须这样做,因为这可能会导致高水平的争用。虽然InterlockedIncrement根本没有争用,但至少不会影响所有性能。