本文主要讨论多线程访问中临界区形成的问题,从本质上讲,临界区的形成是在多线程中,不同的线程访问和操作同一片内存,而形成的。在具体分析之前,我们先来看一个多线程的列子:
我们分别定义了两个线程函数pthread_main1和pthread_main2,在函数中分别对全局变量num进行++和--操作,且操作次数一致,num的初始值是0,最后在主函数中打印结果时,num的值会不会变成0呢?
答案肯定是不会,因为有临界区的存在,我们先来看下运行的结果:
下面我们就根据这个例子来分析下临界区的产生过程。首先,我们先分析下在线程函数中num++和num--过程中,因为++和--在操作过程中表现是一样的,所以以++为例进行分析:
第一步,num是存放在内存的全局数据段中的,++操作时操作系统先把num的值存放到CPU的寄存器中。
第二步,CPU负责计算,将num的值加1.
第三步,操作系统又把寄存器已经加1的值写会到num所在的内存中。
我们期望的方式是在线程1中上述三步全部完成后,再执行线程2中num--的操作,这样最后得到的结果就会是期望值0。
但事实时,也许执行到第一步或者第二步的时候就开始执行num--的操作了。比如:num的初始值是0,当执行到num++的第二步时,num将要被执行第三步变成1之前,线程2开始执行num--的代码,而且执行完成了num--的三个步骤,此时num变成了-1,而这时开始执行num++的第三步,将寄存器中的1写入到了num的内存中,显然现在num又变成1。在分别执行num++和num--之后,num变成了1,显然这并不是我们期望的。
这就是临界区的形成,它不是变量num,而是一段代码num++和num--。让我们再总结一下,它的形成条件:
1、多个线程同时访问一片内存,并企图修改这片内存(在例子里就是num所在的内存)。
#include<stdio.h>
#include<pthread.h>
int num=0;
void* pthread_main1()
{
int i=0;
for(i=0;i<1000000000;i++)
{
num++;
}
}
void* pthread_main2()
{
int i=0;
for(i=0;i<1000000000;i++)
{
num--;
}
}
int main()
{
int i=0;
pthread_t pid[2];
pthread_create((void*)&(pid[0]),NULL,(void*)pthread_main1,NULL);
pthread_create((void*)&(pid[1]),NULL,(void*)pthread_main2,NULL);
for(i=0;i<2;i++)
{
pthread_join(pid[i],NULL);
}
printf("%d\n",num);
return 0;
}
我们分别定义了两个线程函数pthread_main1和pthread_main2,在函数中分别对全局变量num进行++和--操作,且操作次数一致,num的初始值是0,最后在主函数中打印结果时,num的值会不会变成0呢?
答案肯定是不会,因为有临界区的存在,我们先来看下运行的结果:
[Hyman@Hyman-PC multithread]$ gcc ts3.c -D_REENTRANT -lpthread
[Hyman@Hyman-PC multithread]$ ./a.out
-507431
[Hyman@Hyman-PC multithread]$
下面我们就根据这个例子来分析下临界区的产生过程。首先,我们先分析下在线程函数中num++和num--过程中,因为++和--在操作过程中表现是一样的,所以以++为例进行分析:
第一步,num是存放在内存的全局数据段中的,++操作时操作系统先把num的值存放到CPU的寄存器中。
第二步,CPU负责计算,将num的值加1.
第三步,操作系统又把寄存器已经加1的值写会到num所在的内存中。
我们期望的方式是在线程1中上述三步全部完成后,再执行线程2中num--的操作,这样最后得到的结果就会是期望值0。
但事实时,也许执行到第一步或者第二步的时候就开始执行num--的操作了。比如:num的初始值是0,当执行到num++的第二步时,num将要被执行第三步变成1之前,线程2开始执行num--的代码,而且执行完成了num--的三个步骤,此时num变成了-1,而这时开始执行num++的第三步,将寄存器中的1写入到了num的内存中,显然现在num又变成1。在分别执行num++和num--之后,num变成了1,显然这并不是我们期望的。
这就是临界区的形成,它不是变量num,而是一段代码num++和num--。让我们再总结一下,它的形成条件:
1、多个线程同时访问一片内存,并企图修改这片内存(在例子里就是num所在的内存)。
2、修改片内存是通过多步骤的操作完成的(实际上这是必然存在的条件,因为我们只要想修改这片内存的内容,操作系统就会分步骤完成,不会一蹴而就)。
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
Git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL37