本文实现两个线程交替打印,采用的是逐步添加代码,分析每一步代码的作用,若想要看最终版本可直接翻看后面的最终版本。(本文以信号量为例,互斥锁的实现只需将信号量的函数换成相应的互斥锁的函数,互斥锁(信号量)函数不知道的看https://blog.csdn.net/liqiao_418/article/details/83684347)
首先实现两个线程的打印,不加入信号量的应用,代码如下:(注:以下执行结果均是多核环境下的执行结果)
代码说明:在主线程里创建一个函数线程,主线程和函数线程中各写一个循环分别打印。主线程打印完调用函数pthread_join()用来等待函数线程的完成,等函数线程完成后再结束进程(否则可能主线程完成任务后,函数线程还没开始执行,主线程就结束了进程)。
此时运行结果如下:
接下来加入信号量的函数,实现两个线程的交替打印,代码如下:
#include
#include
#include
#include
#include
sem_t sem;//定义全局变量
void* fun(void* arg)
{
printf("fun thread start\n");
int i=0;
for(;i<5;i++)
{
sem_wait(&sem);//相当于P操作
sleep(1);//只是表示循环内部需要执行时间的长短(假设循环内有很多内容,需要执行1s)
printf("fun thread running\n");
sem_post(&sem);//相当于V操作
}
printf("fun thread end\n");
return NULL;
}
int main()
{
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);//创建线程
assert(res==0);//断言线程创建成功
int n=sem_init(&sem,0,1);//对信号量进行初始化
assert(n==0);//断言信号量初始化成功
printf("main thread start\n");
int i=0;
for(;i<8;i++)
{
sem_wait(&sem);//相当于P操作
sleep(1);
printf("main thread running\n");
sem_post(&sem);//相当于V操作
}
res=pthread_join(id,NULL);//用此函数的目的是等待函数线程结束再结束进程
sem_destroy(&sem);//信号量的销毁
printf("main thread end\n");
}
代码说明:在上面代码的基础上,定义全局的sem_t类型的sem,在主线程对信号量进行初始化。主线程和函数线程在循环的最开始加“P操作”(-1操作),循环的最后加“V操作”(+1操作)。最后在主线程即将结束时销毁信号量。
此时运行结果如下:
我们可以看到,加入信号量的使用并没有达到我们所要的结果,函数线程进入后,并没有执行循环里边的内容。这是什么原因呢?
我们在主线程和函数线程循环内的最后一行加入一行代码:
sleep(1);
运行结果如下:
这看起来似乎达到了我们想要的结果,分析一下原因,为什么在循环后面加一行sleep就解决问题了呢?原因是在没有sleep之前主线程执行过程中,每一次主线程刚进行了-1操作,马上就进行下一次循环,然后就进行了+1操作,这在计算机执行起来是很快的,这么短的时间内“较远”的函数线程还没来得及唤醒就又被主线程把资源占用了,所以函数线程只能等到主线程结束再执行。
虽然看起来我们的问题解决了,但是我们并不能依赖于sleep,因为我们并不知道真正的程序代码某一段能执行多长时间。其实,要达到我们的目的,需要用到两个信号量。一个信号量用来控制主线程,一个信号量用来控制函数线程。我们把主线程的信号量初始值设为1,函数线程的信号量初始值设为0,所以函数线程的打印处于阻塞状态,主线程对自己的信号量做“-1操作”,打印完后对函数线程的信号量做“+1操作”;等到下个循环主线程的打印处于阻塞状态,而函数线程此时可以进行打印,函数线程对自己的信号量做“-1操作”,打印完对主线程的信号量做“+1操作”,等到下个循环函数线程的打印处于阻塞状态,主线程就可以打印……以此类推。这就做到了两个进程运行时是串行的。代码如下:
#include
#include
#include
#include
#include
sem_t sem;//控制函数线程的信号量
sem_t sem1;//控制主线程的信号量
void* fun(void* arg)
{
printf("fun thread start\n");
int i=0;
for(;i<5;i++)
{
sem_wait(&sem);//对函数线程信号量P操作
printf("fun thread running\n");
sem_post(&sem1);//对主线程信号量V操作
}
printf("fun thread end\n");
return NULL;
}
int main()
{
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);//创建线程
assert(res==0);//断言线程创建成功
int n=sem_init(&sem,0,0);//初始化函数线程的信号量
assert(n==0);//断言函数线程信号量初始化成功
n=sem_init(&sem1,0,1);//初始化主线程的信号量
assert(n==0);//断言主线程信号量初始化成功
printf("main thread start\n");
int i=0;
for(;i<8;i++)
{
sem_wait(&sem1);//对主线程信号量P操作
printf("main thread running\n");
sem_post(&sem);//对函数线程信号量V操作
}
res=pthread_join(id,NULL);//等待函数线程执行完毕
sem_destroy(&sem);//销毁函数线程信号量
sem_destroy(&sem1);//销毁主线程信号量
printf("main thread end\n");
}
此时执行结果如下:
可以看到函数线程执行完毕后主线程就阻塞了,原因是没有程序为主线程进行“+1操作”了,所以对源代码进行适当修改,使得函数线程结束后主线程依然能够往下执行。
#include
#include
#include
#include
#include
#include
sem_t sem;//控制函数线程的信号量
sem_t sem1;//控制主线程的信号量
bool flag=1;//用来判断函数线程是否退出循环体,1表示没有退出循环
bool flag1=1;//用来判断主线程是否退出循环体,1表示没有退出循环
void* fun(void* arg)
{
printf("fun thread start\n");
int i=0;
for(;i<5;i++)
{
if(flag1)//只有主线程没有退出循环体,才执行此操作
{
sem_wait(&sem);//对函数线程信号量P操作
}
printf("fun thread running\n");
sem_post(&sem1);//对主线程的信号量进行“V操作”
}
flag=0;
sem_post(&sem1);//唤醒可能阻塞的主线程
printf("fun thread end\n");
return NULL;
}
int main()
{
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);//创建线程
assert(res==0);//断言创建线程成功
int n=sem_init(&sem,0,0);//初始化函数线程的信号量
assert(n==0);//断言初始化函数线程信号量成功
n=sem_init(&sem1,0,1);//初始化主线程的信号量
assert(n==0);//断言初始化主线程信号量成功
printf("main thread start\n");
int i=0;
for(;i<8;i++)
{
if(flag)//只有函数线程没有退出循环体,才执行此操作
{
sem_wait(&sem1);//对主线程信号量P操作
}
printf("main thread running\n");
sem_post(&sem);//对函数线程的信号量进行“V操作”
}
flag1=0;
sem_post(&sem);//唤醒可能阻塞的fun函数进程
res=pthread_join(id,NULL);
sem_destroy(&sem);//销毁信号量
sem_destroy(&sem1);
printf("main thread end\n");
}
此时运行结果正确:
但是我们发现每一次都是主线程在等待函数线程执行完毕,然后对信号量进行销毁,在现实中,我们并不知道主线程先结束还是函数线程先结束,如果主线程结束好长时间函数线程才结束的话,在等待函数线程结束的这段时间主线程占用的资源还是没有释放这就造成资源的浪费。所以我们希望最后结束的线程来销毁信号量,这里,引入注册函数atexit(),atexit()函数在进程结束时调用,所以不管主线程先结束还是函数线程先结束,atexit()函数是在最后结束的进程中调用,对信号量进行销毁。只需对上述代码稍作修改,就得到:
#include
#include
#include
#include
#include
#include
sem_t sem;//控制函数线程的信号量
sem_t sem1;//控制主线程的信号量
bool flag=1;//用来判断函数线程是否退出循环体,1表示没有退出循环
bool flag1=1;//用来判断主线程是否退出循环体,1表示没有退出循环
void* fun(void* arg)
{
printf("fun thread start\n");
int i=0;
for(;i<5;i++)
{
if(flag1)//只有主线程没有退出循环体,才执行此操作
{
sem_wait(&sem);//对函数线程信号量P操作
}
printf("fun thread running\n");
sem_post(&sem1);//对主线程的信号量进行“V操作”
}
flag=0;
sem_post(&sem1);//唤醒可能阻塞的主线程
printf("fun thread end\n");
return NULL;
}
void destroy()//新增代码
{
printf("destroy\n");
sem_destroy(&sem);//销毁信号量
sem_destroy(&sem1);
}
int main()
{
atexit(destroy);//注册函数
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);//创建线程
assert(res==0);//断言创建线程成功
int n=sem_init(&sem,0,0);//初始化函数线程的信号量
assert(n==0);//断言初始化函数线程信号量成功
n=sem_init(&sem1,0,1);//初始化主线程的信号量
assert(n==0);//断言初始化主线程信号量成功
printf("main thread start\n");
int i=0;
for(;i<8;i++)
{
if(flag)//只有函数线程没有退出循环体,才执行此操作
{
sem_wait(&sem1);//对主线程信号量P操作
}
printf("main thread running\n");
sem_post(&sem);//对函数线程的信号量进行“V操作”
}
flag1=0;
sem_post(&sem);//唤醒可能阻塞的fun函数进程
/*res=pthread_join(id,NULL);
sem_destroy(&sem);
sem_destroy(&sem1);*/
printf("main thread end\n");
}
运行结果如下:
这就完成了两个线程的交替打印,用互斥锁实现两个线程的交替打印也是一样的,只需将信号量的函数换成相应的互斥锁的函数就行。