【转】用信号量(互斥锁)实现两个线程交替打印

转载自https://blog.csdn.net/liqiao_418/article/details/83715863,面试的时候被问到了怎么交替打印数字通过两个线程,当时有点懵,知道考察的点是线程的同步和线程互斥,能想到的也是互斥锁和条件变量,但是具体怎么实现当时却没有想到,网上搜索了一下,发现这篇文章通过互斥锁和信号量来实现,最关键的还想到了使用两个标志位的小技巧来处理各种异常情况,考虑的非常细致。

本文实现两个线程交替打印,采用的是逐步添加代码,分析每一步代码的作用,若想要看最终版本可直接翻看后面的最终版本。(本文以信号量为例,互斥锁的实现只需将信号量的函数换成相应的互斥锁的函数,互斥锁(信号量)函数不知道的看https://blog.csdn.net/liqiao_418/article/details/83684347),线程基础看(https://blog.csdn.net/liqiao_418/article/details/83506429

首先实现两个线程的打印,不加入信号量的应用,代码如下:(注:以下执行结果均是多核环境下的执行结果)

代码说明:在主线程里创建一个函数线程,主线程和函数线程中各写一个循环分别打印。主线程打印完调用函数pthread_join()用来等待函数线程的完成,等函数线程完成后再结束进程(否则可能主线程完成任务后,函数线程还没开始执行,主线程就结束了进程)。

此时运行结果如下:

接下来加入信号量的函数,实现两个线程的交替打印,代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<semaphore.h>
#include<pthread.h>
 
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<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<semaphore.h>
#include<pthread.h>
 
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<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<semaphore.h>
#include<pthread.h>
#include<stdbool.h>
 
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<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<semaphore.h>
#include<pthread.h>
#include<stdbool.h>
 
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");
}
运行结果如下:

这就完成了两个线程的交替打印,用互斥锁实现两个线程的交替打印也是一样的,只需将信号量的函数换成相应的互斥锁的函数就行。
 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值