快捷搜索:  汽车  科技

linux多线程并发教程交流(linuxc编程之多线程)

linux多线程并发教程交流(linuxc编程之多线程)三步以后,g_count的值被顺利加1。3.把寄存器eax中的内容移动到内存g_count的地址g_count ;被分为三步操作:1.把g_count的内容从内存中移动到寄存器eax2.把寄存器eax加1

在多线程中操作全局变量一般都会引起线程冲突,为了解决线程冲突,引入原子操作。

1.线程冲突

#include <stdio.h> #include <stdlib.h> #include <process.h> #include <Windows.h> int g_count = 0; void count(void *p) { Sleep(100); //do some work //每个线程把g_count加1共10次 for (int i = 0; i < 10; i ) { g_count ; } Sleep(100); //do some work } int main(void) { printf("******多线程访问全局变量演示***by David***\n"); //共创建10个线程 HANDLE handles[10]; for (int i = 0; i < 10; i ) { for (int j = 0; j < 10; j ) { handles[j] = _beginthread(count 0 NULL); } WaitForMultipleObjects(10 handles 1 INFINITE); printf("%d time g_count = %d\n" i g_count); //重置 g_count = 0; } getchar(); return 0; }

运行

linux多线程并发教程交流(linuxc编程之多线程)(1)

理论上,g_count的最后结果应该是100,可事实却并非如此,不但结果不一定是100,而且每次的结果还很可能不一样。原因是,多个线程对同一个全局变量进行访问时,特别是在进行修改操作,会引起冲突。详细解释:

设置断点,查看反汇编

linux多线程并发教程交流(linuxc编程之多线程)(2)

g_count ;被分为三步操作:

1.把g_count的内容从内存中移动到寄存器eax

2.把寄存器eax加1

3.把寄存器eax中的内容移动到内存g_count的地址

三步以后,g_count的值被顺利加1。

cpu执行的时间片内包含多条指令,每条指令执行期间不会被打断,但如果一个操作包含多条指令,则很有可能该操作会被打断。g_count ;就是这样的操作。

g_count被顺利加到100的前提:每次加1,都建立在上一次加1顺利完成的基础上。也就是说,如果上一次加1被打断,这一次的加1就得不到上一次加1的累积效果。自然,最后的结果,多半会小于100。

2.原子操作

所谓原子操作,是指不会被线程调度机制打断的操作,操作一旦开始,就得执行到结束为止。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。原子操作一般靠底层汇编实现。

在头文件winnt.h中提供了很多的原子操作函数,它们使用自加锁的方式,保证操作的原子性,如自增操作

InterlockedIncrement,

函数原型

LONG CDECL_NON_WVMPURE InterlockedIncrement(
_Inout_ _Interlocked_operand_ LONG volatile *Addend
);

其它相关操作,请自行查看头文件。

使用该函数,我们修改线程函数count。修改很简单,只需把g_count 改为InterlockedIncrement((LONG)&g_count);即可。

运行如下

linux多线程并发教程交流(linuxc编程之多线程)(3)

显然,在原子操作下,我们肯定是可以得到正确结果的。

需要C/C Linux服务器架构师学习资料私信“资料”(资料包括C/C ,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

linux多线程并发教程交流(linuxc编程之多线程)(4)

猜您喜欢: