互斥量与条件变量

来源:互联网 发布:数据安全管理方法 编辑:程序博客网 时间:2024/06/01 14:52

http://blog.chinaunix.net/uid-27177626-id-3765181.html

http://blog.csdn.net/bolike/article/details/9025389

一、概述

在多线程或者多进程共享数据时,为了保证数据的完整性和正确性,,使用同步机制来实现。互斥锁和条件变量均是出自posix.1线程标准,可以被用来同步一个进程中的多个线程,还可以用来同步共享一块内存空间的多个进程。

比如典型的,在多处理线程编程生产者消-费者问题的时候,最值得注意的就是那些被共享的数据区,因为无法知道在哪个时候哪个线程在使用着这些共享数据,也无法预知哪个线程会先运行它,哪个线程后运行它,所以对这些资源做合理的分配和正确的使用是非常有必要的。在Linux中,提供了互斥锁、条件变量以及信号量来对共享资源进行保护。关于信号量在本篇中暂不做说明,只讨论互斥锁和条件变量在同步中的应用。

二、互斥锁

互斥锁(英文:Mutualexclusion,常缩写为Mutex)是一种常用在多线程编程中,防止多个线程对一个公共资源做读写操作的机制,以保证共享操作的数据的完整性。互斥锁也可以从字面意思来看,就是相互排斥的锁,它是最基本的进程或者线程间同步的方法,用来保护临界区,以保证任何时候只有一个线程或者进程在访问共享资源(如共享的代码段)。保护一个临界区的代码形式大致如下:

lock_the_mutex(...)

临界区

unlock_the_mutex(...)

posix的互斥锁被声明为pthread_mutex_t类型的变量。互斥锁也分为静态互斥锁和动态互斥锁两类,如果是静态互斥锁,常常会把它初始化为一个常值PTHREAD_MUTEX_INITIALIZER,就像这样:

staticpthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

在内核中关于阻塞互斥锁的具体实现可以查看:include/linux/mutex.h这个文件。

如果互斥锁是动态分配的,比如通过malloc来分配的或者是分配在共享内存中,那么真正使用它之前一定要用pthread_mutex_init函数来对它做初始化。下面的三个函数是对互斥锁进行上锁和解锁:

#include

intpthread_mutex_lock(pthread_mutex_t *mptr);

intpthread_mutex_trylock(pthread_mutex_t *mptr);

intpthread_mutex_unlock(pthread_mutex_t *mptr);

对以上三个函数,如果调用成功均返回0,失败返回相应的errno值。

互斥锁还有协作性,就是说,假如共享数据是一个链表,那么对该链表进行操作的所有线程都必须在其执行实际操作之前获取到该互斥锁。

三、互斥锁实现的生产者-消费者模型

以多生产者,单消费者的模型实践了互斥锁的作用。

问题描述:一个或者多个生产者(线程或者进程)创建着一个个的数据条目,然后这些条目由一个消费者(线程或者进程)处理。

实现方式:

1、只考虑多个生产者之间的同步互斥(producer_consumer1.c)

https://github.com/helianthuslulu/LINUX_IPC

2、生产者和消费者之间的同步,消费者采用轮询方式(producer_consumer2.c)

https://github.com/helianthuslulu/LINUX_IPC

四、条件变量

互斥锁的确能很好的实现进程/线程之间的同步问题,但是它是通过锁机制来实现的,就是仅仅通过加锁和解锁实现同步,效率比较低,于是就有了条件变量(ConditionVariable),条件变量允许一个进程或者是线程睡眠直到某个事件为止。

互斥锁用于上锁,而条件变量用于等待。条件变量是类型为pthread_cond_t的变量。具体来说条件变量可以适用的情况:在线程同步中存在这样的一种情况,某个线程A需要等待某个条件成立之后才能继续往下执行,如果当前这个条件不成立,那么该线程就阻塞等待,而如果某个时刻另一个线程B在执行的过程中使得这个条件成立了,那么就会唤醒相称A继续往下执行。

pthread库中用条件变量来阻塞等待一个条件,或者唤醒等待这个条件的进程。

条件变量操作相关的函数:

#include

intpthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);

intint pthread_cond_signal(pthread_cond_t *cond);

intpthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t*attr);

intpthread_cond_destroy(pthread_cond_t *cond);

intpthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_tmytex,const struct timespec *abstime);

intpthread_cond_broadcast(pthread_cond_t *cond);

 

pthread_cond_wait函数是用于条件变量的等待;

pthread_cond_signal用于条件变量的通知;

pthread_cond_init用户条件变量初始化;

pthread_cond_destroy用于条件变量的摧毁;

pthread_cond_timedwait也是用于条件变量的等待,不过它与pthread_cond_wait的区别在于,在pthread_cond_timewait中,如果等待时间达到或是超过所引用的参数*abstime,它将结束并返回错误ETIME

不过这里的“条件”可以是我们编程者自己选择,然后会在具体的代码实现中测试这种条件。

条件变量是利用线程间共享的全局变量来进行同步的一种机制,它的实现主要有两个动作:

(1)一个线程等待条件变量的成立而被挂起;

(2)由另外某个线程使得条件变量成立,给出条件成立的信号,唤醒该睡眠线程。

条件变量中条件的检测是在互斥锁的保护下进行的,所以说每个条件变量总是会有一个互斥锁与之关联。

在具体使用条件变量之前也是要进行初始化的,可以在单条语句中生成并且初始化一个条件变量,像这样:pthread_cond_tmy_condition = PTHREAD_COND_INITIALIZER;

五、使生产者消费者模型来解释条件变量的使用

还是第三部分所提到的问题,不过使用了条件变量来通知消息,而不必在总是让消费者轮询,对之前的代码再次做了改进:

producer_consumer3.c

https://github.com/helianthuslulu/LINUX_IPC


一。互斥量和条件变量简介

       互斥量(mutex)从本质上说是一把,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥锁加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。

       条件变量(cond)是在多线程程序中用来实现"等待--》唤醒"逻辑常用的方法。条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使“条件成立”。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起线程在改变条件状态前必须首先锁住互斥量,函数pthread_cond_wait把自己放到等待条件的线程列表上,然后对互斥锁解锁(这两个操作是原子操作)。在函数返回时,互斥量再次被锁住


二。为什么存在条件变量

       首先,举个例子:在应用程序中有连个线程thread1,thread2,thread3和thread4,有一个int类型的全局变量iCount。iCount初始化为0,thread1和thread2的功能是对iCount的加1,thread3的功能是对iCount的值减1,而thread4的功能是当iCount的值大于等于100时,打印提示信息并重置iCount=0。

       如果使用互斥量,线程代码大概应是下面的样子:

       thread1/2:

       while (1)

       {

             pthread_mutex_lock(&mutex);

             iCount++;

             pthread_mutex_unlock(&mutex);

       }

       thread4:

       while(1)

       {

             pthead_mutex_lock(&mutex);

             if (100 <= iCount)

             {

                   printf("iCount >= 100\r\n");

                   iCount = 0;

                   pthread_mutex_unlock(&mutex);

             }

             else

             {

                   pthread_mutex_unlock(&mutex);

             }

       }

       在上面代码中由于thread4并不知道什么时候iCount会大于等于100,所以就会一直在循环判断,但是每次判断都要加锁、解锁(即使本次并没有修改iCount)。这就带来了问题一,CPU浪费严重。所以在代码中添加了sleep(),这样让每次判断都休眠一定时间。但这由带来的第二个问题,如果sleep()的时间比较长,导致thread4处理不够及时,等iCount到了很大的值时才重置。对于上面的两个问题,可以使用条件变量来解决。

      首先看一下使用条件变量后,线程代码大概的样子:

      thread1/2:

       while(1)

       {

               pthread_mutex_lock(&mutex);

               iCount++;

               pthread_mutex_unlock(&mutex);

               if (iCount >= 100)

               {

                      pthread_cond_signal(&cond);

               }

       }         

       thread4:

       while (1)

       {

              pthread_mutex_lock(&mutex);

              while(iCount < 100)

              {

                     pthread_cond_wait(&cond, &mutex);

              }

              printf("iCount >= 100\r\n");

              iCount = 0;

              pthread_mutex_unlock(&mutex);

       }

       从上面的代码可以看出thread4中,当iCount < 100时,会调用pthread_cond_wait。而pthread_cond_wait在上面应经讲到它会释放mutex,然后等待条件变为真返回。当返回时会再次锁住mutex。因为pthread_cond_wait会等待,从而不用一直的轮询,减少CPU的浪费。在thread1和thread2中的函数pthread_cond_signal会唤醒等待cond的线程(即thread4),这样当iCount一到大于等于100就会去唤醒thread4。从而不致出现iCount很大了,thread4才去处理。      需要注意的一点是在thread4中使用的while (iCount < 100),而不是if (iCount < 100)。这是因为在pthread_cond_singal()和pthread_cond_wait()返回之间有时间差,假如在时间差内,thread3又将iCount减到了100以下了,那么thread4就需要在等待条件为真了。
0 0