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

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

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

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

首先,您需要确定您要实现的目标,以及不同线程的指令可以通过什么方式交织以防止发生这种情况。

C ++x的递增运算符通常实现为从存储器读取i的值到寄存器中; 递增寄存器; 将值写入内存:

     r 1 ←x 全球
     r 1 ←r 1 + 1
     x global ←r 1

所以x global的值加1。

如果你有两个并行的线程,那么它们可以破坏性地交错

     initial x global = 99

     r 1 ←x 全球     
    比较r 1 100 
                     r 2 ←x 全球          
                    比较r 2 100 
     r 1 ←r 1 + 1 == 100 
                     r 2 ←r 2 + 1 == 100
     x global ←r 1 == 100
                     x global ←r 2 == 100
     r 1 ←x 全球     
    比较r 1 100 
    停
                     r 2 ←x 全球     
                    比较r 1 100 
                    停
     final x global = 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 <100结束:

     initial x global = 98
                     r 2 ←x 全球          
     r 1 ←x 全球     
    比较r 1 100 
     r 1 ←r 1 + 1 == 99
     x global ←r 1     
     r 1 ←x 全球     
    比较r 1 100 
     r 1 ←r 1 + 1 == 100
     x global ←r 1     
     r 1 ←x 全球     
    比较r 1 100 
    停
                    比较r 2 100 
                     r 2 ←r 2 + 1 == 99
                     x global ←r 2      
                     ...
     final x global = 99

因此,左线程的第二个增量被第一个增量覆盖,并且它将以x <100的全局可见值终止。

您可能知道所有这些,并且可能希望使用一种机制来防范它。

我说“可能”因为你的要求不明确 - 当x达到100时,上面的线程终止; 要求不说它不说那里。

因此,由于没有线程将在没有写入x global ←100的情况下终止,所以实际上可以在没有任何锁定的情况下满足该要求,但是x可以递增n * 100次而不是100次。 (如果限制大于一个字节,则在某些平台上写入x可能是非primefaces的,如果来自不同线程的字节混合在一起,则可能导致无限循环,但是对于不会发生的限制为100 )

一种技术是使用互斥锁 ,当一个线程持有互斥锁上的锁时,该互斥锁阻止其他线程运行。 如果在读取x global之前获取锁定,并且在写入x global之前未释放锁定,则线程的读取和写入不能交错。

     initial x global = 98

    锁(互斥) 
    互斥锁定 
                    锁(互斥) 
                    受阻 
     r 1 ←x 全球     
    比较r 1 100 
     r 1 ←r 1 + 1 == 99
     x global ←r 1     

    发布(互斥)
                    互斥锁定

                     r 2 ←x 全球          
                    比较r 2 100 
                     r 2 ←r 2 + 1 == 100
                     x global ←r 2      

                    发布(互斥)

    锁(互斥) 
    互斥锁定 
     r 1 ←x 全球     
    比较r 1 100 
    发布(互斥)
    停
                     ...
     final x global = 100

在pthread之外,您可能希望使用平台的比较和交换操作( gcc中的 __sync_val_compare_and_swap ),该操作将地址设置为旧值和新值,并且如果它等于,则将地址处的内存primefaces设置为新值旧的价值。 这使您可以将逻辑编写为:

 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 x global = 98
    初始v 1 = 0
    初始v 2 = 0

     cmp v 1 100
     x_prev 1 ←CASV(x global ,v 1 ,v 1 + 1)= 98(设置失败,x == 98)

                     cmp v 2 100
                     x_prev 2 ←CASV(x global ,v 1 ,v 1 + 1)= 98(设置失败,x == 98)

     v 1 ←x_prev 1 = 98 // x_prev!= v
                     v 2 ←x_prev 2 = 98
                     cmp v 2 100
                     x_prev 2 ←CASV(x global ,v 1 ,v 1 + 1)= 98(设置成功,x == 99)

                     v 2 ←x_prev 2 + 1 = 99 //作为x_prev == v

     cmp v 1 100
     x_prev 1 ←CASV(x global ,v 1 ,v 1 + 1)= 99(设置失败,x == 99)
     v 1 ←x_prev 1 = 99 //作为x_prev!= v

     cmp v 1 100
     x_prev 1 ←CASV(x global ,v 1 ,v 1 + 1)= 99(设置成功,x == 100)
     v 1 ←x_prev 1 + 1 = 100 //作为x_prev == v

                     cmp v 2 100
                     x_prev 2 ←CASV(x global ,v 1 ,v 1 + 1)= 100(设置失败,x == 100)

                     v 2 ←x_prev 2 = 100 //为x_prev!= v
     cmp v 1 100
                     cmp v 2 100
    停
                    停

在每个循环中,x globalprimefaces地设置为r 1 + 1的值,当且仅当其先前值为r 1时 ; 如果不是,则在CASV操作期间将r 1设置为x global测试的值。 这减少了大多数实现中锁定的时间量(虽然它仍然需要在CAS操作期间锁定内存总线,但只有那些操作将被序列化。因为在多核上执行CAS是昂贵的,它可能赢了这样一个简单的案例要好得多。)

您需要一个互斥锁来保护变量。 每个线程将锁定互斥锁,增加变量并释放互斥锁。 不执行此操作的每个线程都是一个流氓线程。

你需要的是一个关键部分。 在windows下,这将是EnterCriticalSection,但在pthread环境中,等效的是pthread_mutex_lock 。 请看这里的一些指示。

如果X> = 100,如果每个线程都可以退出,我认为InterlockedIncrement就足够了。

我永远不会使用关键部分,除非我真的必须这样做,因为这可能导致高水平的争用。 虽然InterlockedIncrement根本没有争用,但至少不会影响所有性能。