Linux---详谈线程的同步与互斥,互斥量以及条件变量

  • 大部分情况下,线程使用的都是局部变量,保存在自己的私有栈结构里,但是有的时候又会多线程访问共享资源,如全局变量,这时候如果操作过于频繁,或者线程数量较多,就会产生一些问题,因为毕竟不是所有的操作都是原子性的(要么不做,要么就做完,不存在中间时刻)
  • 下面模拟买票场景,大家看完就会明白问题所在,代码很简单
     int ticket = 20;//假设一共有20张票(全局变量)

    void* thread_run(void* arg)
    {
        const void* msg = (const void*)arg;
       while(1)
       {
           if(ticket>0)
           {
               usleep(1000);//让每个线程等待一下,最后现象会更明显
               printf("%s get a ticket,ticket: %d\n",msg,ticket);//打印当前剩余票数和购票人
               ticket--;//票数减1
           }else
           {
               break;
           }
       }
    }


    int main()
    {
        pthread_t tid1,tid2,tid3,tid4;
        //创建四个线程,作为四个购票者
        pthread_create(&tid1,NULL,thread_run,"thread1");
        pthread_create(&tid2,NULL,thread_run,"thread2");
        pthread_create(&tid3,NULL,thread_run,"thread3");
        pthread_create(&tid4,NULL,thread_run,"thread4");
        pthread_join(tid1,NULL);
        pthread_join(tid2,NULL);
        pthread_join(tid3,NULL);
        pthread_join(tid4,NULL);


    }

远行结果:

这里写图片描述

  • 我们发现结果中出现了负数的情况,这显然是不合理的,为什么会产生这种情况呢?
  • 答案很简单,四个线程在同时访问一个全局变量ticket,都进行了 减减操作,而减减的操作并不是原子操作,它会先把ticket变量从内存加载到寄存器中,更新寄存器里面的值,执行减减操作,再从寄存器写回其原内存地址中。所以在此期间,很有可能多个进程同时访问该变量,导致结果异常,那有什么解决办法呢?
  • 解决问题的方法也很简单,就是规定一个线程在申请到该资源后,其他线程不能再继续申请,只能等待第一个线程释放掉该资源后,才能进行申请
  • 举个例子,你想去超市买苹果,超市里只有几个苹果了,大家都争先恐后的去抢,而你又很幸运的第一个冲了进去,这时你怎么保证苹果拿到手后不被人抢走?很简单,当然是拿把锁把门锁上了,开开心心的吃完苹果后,再把锁开开不就完了么。线程中也是如此,为了保证线程之间的工作互不影响,我们引入了互斥与同步的概念,以及互斥量和条件变量
互斥

概念:事件A与事件B在任何一次试验中都不会同时发生,则称事件A与事件B互斥。
线程互斥:两个或多个线程不会同时访问同一块临界资源(共享资源),即多个线程互斥地访问同一块资源

为了实现多线程互斥行为,Linux下提供的这把锁被命名为互斥量。

互斥量的接口

初始化互斥量

  • 方法1:静态分配
pthread_mutex_t = PTHREAD_MUTEX_INITIALIZER
  • 方法2:动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数:
    mutex:要初始化的互斥量
    attr:NULL

销毁互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthreade_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

调用pthread_lock时,可能会遇到以下情况

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞,等待互斥量解锁

售票系统代码改进后:

    int ticket = 20;
    pthread_mutex_t lock;//创建互斥锁,因为互斥锁要让每个进程都能看到,所以要创建成全局的                                                                                                             

    void* thread_run(void* arg)
    {
        const void* msg = (const void*)arg;
       while(1)
       {
           pthread_mutex_lock(&lock);//在访问ticket变量前上锁
           if(ticket>0)
           {
               usleep(1000);
               printf("%s get a ticket,ticket: %d\n",msg,ticket);
               ticket--;
               pthread_mutex_unlock(&lock);//访问完后将互斥锁释放
           }else
           {
               pthread_mutex_unlock(&lock);//释放互斥锁
               break;
           }
       }
    }


    int main()
    {
        pthread_t tid1,tid2,tid3,tid4;

        pthread_mutex_init(&lock,NULL);//初始化互斥锁
        pthread_create(&tid1,NULL,thread_run,"thread1");
        pthread_create(&tid2,NULL,thread_run,"thread2");
        pthread_create(&tid3,NULL,thread_run,"thread3");
        pthread_create(&tid4,NULL,thread_run,"thread4");

        pthread_join(tid1,NULL);
        pthread_join(tid2,NULL);
        pthread_join(tid3,NULL);
        pthread_join(tid4,NULL);

        pthread_mutex_destroy(&lock);//销毁互斥锁,一定要在等待线程之后再销毁,因为其余四个线程在执行购票操作
        return 0;                    //时,主线程在阻塞式的等待,如果主线程先把锁销毁了,就会导致其余线程阻塞
                                     //因为都在等待申请锁,或者一个线程在未释放锁时,锁已经被销毁了 
    }  

运行多次结果依旧正确,实现了多线程间的互斥行为,但是即使加上了互斥锁,也还是有小问题,大家发现了没,全部20张票都被线程1买走了,这能忍?各位都是要买票回家的,怎么着也应该都有票才是,为了解决这个问题,我们就引入了同步的概念

同步

概念:指对在一个系统中所发生的事件之间进行协调,在时间上出现一致性与统一化的现象。
线程间的同步:两个或两个以上的线程协同的访问共享资源,即线程A访问完之后,他不急着再去申请该资源,而是让给线程B去访问。

为了实现多线程同步行为,Linux引入了条件变量,用法和互斥量很相似

  • 举例:假设有一个生产者,一个消费者,消费者只干一件事,就是买香蕉,生产者也只生产香蕉,当消费者去买的时候,发现香蕉没了,他不会每时每刻都去检查香蕉送来了没,而是会等待生产者的消息,生产者送来香蕉后,就会给消费者打电话,让他去取香蕉,这样两人互不干扰对方的事情,并且达到了一种协同。
  • 条件变量类似于那个电话,当线程A发现它的条件不成立时(香蕉没了),就会在该条件下等待,并且释放掉锁(你总不能天天守着超市还不给人家送货的开门吧),而线程B得到锁后,就会去执行它的工作,并且改变条件变量,唤醒等待中的线程A(相当于打电话给他),并且释放掉锁,这时线程A被唤醒,并会重新得到锁,去执行接下来的代码,实现同步

条件变量函数

初始化条件变量

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
    cond:要初始化的条件变量
    attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond);

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
    cond:要在这个变量上等待,并且在等待期间要释放锁;如果被正常或异常唤醒后,会重新获得锁并从等待处开始执行
    mutex:互斥量

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒一群线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒单个线程

代码实现:

    volatile int banana = 0;
    pthread_cond_t cond1;//创建条件变量
    pthread_mutex_t lock;//创建锁

    void *comsumer(void *arg)
    {
        while(1)
        {
            pthread_mutex_lock(&lock);//先申请互斥锁
            while(banana<=0)
            {
                printf("I want to eat a banana\n");
                pthread_cond_wait(&cond1,&lock);//此时没有香蕉,就把锁释放,并在cond1号条件变量下等待
            } else{
                banana--;
                printf("I eat a banana!\n");
            }
            pthread_mutex_unlock(&lock);//释放锁
        }
    }

    void *product(void *arg)

    {
        while(1)
        {
            pthread_mutex_lock(&lock);//生产者申请锁
            banana++;
            printf("I put a banana\n");

            int r = pthread_cond_broadcast(&cond1);//如果唤醒失败,则打印出错信息
            if ( r != 0  ) {
                fprintf(stderr, "=======%s\n", strerror(r));
            }
            pthread_mutex_unlock(&lock);
            sleep(5);
        }
    }

    int main()
    {

        pthread_t com,pro;
        pthread_cond_init(&cond1,NULL);
        pthread_mutex_init(&lock,NULL);

        pthread_create(&com,NULL,comsumer,NULL);
        pthread_create(&pro,NULL,product,NULL);

        pthread_join(com,NULL);
        pthread_join(pro,NULL);

        pthread_cond_destroy(&cond1);//销毁条件变量也要在回收线程之后,原因同互斥量
        pthread_mutex_destroy(&lock);


        return 0;
    }   

这里写图片描述

运行结果显示两者协同工作,互不干扰

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值