C线程编程 - 增加共享变量

时间:2009-10-07 18:34:42

标签: c multithreading pthreads

嘿伙计......所以我试图刷新我的C线程,我发现的问题是:

给定全局变量int x = 0;实现函数void useless(int n),它创建n个线程,在循环中将x增加1,每个线程在x达到100时终止。

我只是没有线程处理,需要一个坚实的例子来建立我的基础。这必须尽可能使用pthread系统调用。

4 个答案:

答案 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根本没有争用,但至少不会影响所有性能。