嘿伙计……所以我试图刷新我的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 global将primefaces地设置为r 1 + 1的值,当且仅当其先前值为r 1时 ; 如果不是,则在CASV操作期间将r 1设置为x global测试的值。 这减少了大多数实现中锁定的时间量(虽然它仍然需要在CAS操作期间锁定内存总线,但只有那些操作将被序列化。因为在多核上执行CAS是昂贵的,它可能赢了这样一个简单的案例要好得多。)
您需要一个互斥锁来保护变量。 每个线程将锁定互斥锁,增加变量并释放互斥锁。 不执行此操作的每个线程都是一个流氓线程。
你需要的是一个关键部分。 在windows下,这将是EnterCriticalSection,但在pthread环境中,等效的是pthread_mutex_lock
。 请看这里的一些指示。
如果X> = 100,如果每个线程都可以退出,我认为InterlockedIncrement就足够了。
我永远不会使用关键部分,除非我真的必须这样做,因为这可能导致高水平的争用。 虽然InterlockedIncrement根本没有争用,但至少不会影响所有性能。
以上就是c/c++开发分享C线程编程 – 增加共享变量相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/562087.html