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

原创 2018年04月16日 17:37:05
  • 大部分情况下,线程使用的都是局部变量,保存在自己的私有栈结构里,但是有的时候又会多线程访问共享资源,如全局变量,这时候如果操作过于频繁,或者线程数量较多,就会产生一些问题,因为毕竟不是所有的操作都是原子性的(要么不做,要么就做完,不存在中间时刻)
  • 下面模拟买票场景,大家看完就会明白问题所在,代码很简单
     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;
    }   

这里写图片描述

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

【线程的同步与互斥 (互斥量 条件变量 信号量)】生产者与消费者模型

线程线程是进程中的一个独立的执行流,由环境(包括寄存器集和程序计数器)和一系列要执行的置零组成。所有进程至少有一个线程组成,多线程的进程包括多个线程,所有线程共享为进程分配的公共地址空间,所以文本段(...
  • xs_520
  • xs_520
  • 2017-07-02 20:51:52
  • 1032

Linux多线程间同步与互斥---条件变量(Conditoin Variable)

Linux多线程间同步与互斥---条件变量(Conditoin Variable)
  • Li_Ning_
  • Li_Ning_
  • 2016-08-22 15:17:13
  • 799

C++11 多线程同步 互斥锁 条件变量

在多线程程序中,线程同步(多个线程访问一个资源保证顺序)是一个非常重要的问题,Linux下常见的线程同步的方法有下面几种: 互斥锁 条件变量 信号量 这篇博客只介绍互斥量和条件变量的使用。互斥锁和条件...
  • yangbodong22011
  • yangbodong22011
  • 2017-03-04 21:21:26
  • 2461

浅析线程间通信一:互斥量和条件变量

线程同步的目的简单来讲就是保证数据的一致性。在Linux中,常用的线程同步方法有互斥量( mutex )、读写锁和条件变量,合理使用这三种方法可以保证数据的一致性,本文将讨论互斥量和条件变量的使用,并...
  • MaximusZhou
  • MaximusZhou
  • 2015-01-03 19:57:00
  • 2720

线程同步机制(互斥量,读写锁,自旋锁,条件变量,屏障)

先知:      (1)线程是由进程创建而来,是cpu调度的最小单位。      (2)每个进程都有自己独立的地址空间,而进程中的多个线程共用进程的资源,他们只有自己独立的栈资源。 线程...
  • poison_biti
  • poison_biti
  • 2017-08-04 10:22:36
  • 929

为什么有了互斥锁还要条件变量(二者的不同)

一。互斥量和条件变量简介 互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线...
  • championhengyi
  • championhengyi
  • 2016-08-04 15:58:28
  • 946

linux多线程-----同步对象(互斥量、读写锁、条件变量)的属性

线程具有属性,同样用于线程同步的对象也有属性,主要有互斥量、读写锁和条件变量的属性。...
  • Linux_ever
  • Linux_ever
  • 2016-04-01 21:47:09
  • 896

线程同步方法:互斥量,信号量,原子锁

///VC, 线程同步方法:互斥量,信号量#define StartThread(thrFun) CloseHandle(CreateThread(NULL,0,thrFun,NULL,0,NULL)...
  • wabil
  • wabil
  • 2016-06-06 09:42:04
  • 1048

C++封装互斥量和条件变量

互斥量 (1)互斥量是保护临界区的另一种方法,当执行线程在临界区的执行时间很长时,那么就最好使用互斥量了,否则会造成其他的线程将会在临界区外忙等,浪费CPU时间;此时其他线程发现临界区已经被互斥量锁...
  • linuxcprimerapue
  • linuxcprimerapue
  • 2015-08-04 19:46:46
  • 648

多线程用互斥锁和条件变量实现生产者和消费者-------循环任务队列

互斥锁与条件变量简介 在多线程的环境中,全局变量会被各线程共享,因此在操作全局变量的时候需要采用锁机制,在linux里最常用的锁就是互斥锁,互斥锁使用方法如下 //线程A pthread_mutex_...
  • u010424605
  • u010424605
  • 2015-01-19 13:59:07
  • 1319
收藏助手
不良信息举报
您举报文章:详谈线程的同步与互斥,互斥量以及条件变量的实现
举报原因:
原因补充:

(最多只允许输入30个字)