您的位置:首页 > 其它

互斥锁和条件变量

2012-09-23 10:33 330 查看
互斥锁和条件变量是出自Posix线程标准,用来同步一个进程中各个线程的,同时也可以用来同步几个进程间的,不过这需要此互斥锁和条件变量是存放在多个进程间共享的某个内存区的。

互斥锁上锁与解锁:

 

1
#include <pthread.h>
2
 
3
int
 
pthread_mutex_lock(pthread_mutex_t *mutex);
4
int
 
pthread_mutex_trylock(pthread_mutex_t *mutex);
5
int
 
pthread_mutex_unlock(pthread_mutex_t *mutex);
 

互斥锁初始化的问题:

 

可以通过两种方式初始化一个互斥锁变量:

1,如果互斥锁变量是静态分配的,那么使用如下形式初始化:

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

2,如果互斥锁是动态分配的,那么我么可以用pthread_mutex_init函数初始化它。

 

 

1
int
 
pthread_mutex_init(pthread_mutex_t *restrict mutex,
2
 
3
       
const
 
pthread_mutexattr_t *restrict attr);
 

这两者有个很关键的地方,静态分配的话,只能使用默认的属性,而对于动态分配的,我们可以设置属性。

 

 

条件变量:等待与信号发送:

互斥锁有个劣势,那就是他仅仅是上锁和解锁,效率低,这时我们可以通过引入条件变量来解决问题,它允许一个线程或进程睡眠到某个事件为止。

 

1
#include <pthread.h>
2
 
3
int
 
pthread_cond_timedwait(pthread_cond_t *restrict cond,
4
       
pthread_mutex_t *restrict mutex,
5
       
const
 
struct
 
timespec *restrict abstime);
6
int
 
pthread_cond_wait(pthread_cond_t *restrict cond,
7
       
pthread_mutex_t *restrict mutex);
1
#include <pthread.h>
2
 
3
int
 
pthread_cond_broadcast(pthread_cond_t *cond);
4
int
 
pthread_cond_signal(pthread_cond_t *cond);
 每一个条件变量总是有一个互斥锁与之关联,我们再等待某个条件为真时,还会指定条件变量的地址和所关联的互斥锁的地址,这是什么意思呢?就是说,在使用pthread_cond_wait函数之前,我们要先用一个互斥锁锁住,然后当我们调用pthread_cond_wait函数进入睡眠。注意:该函数原子的执行两个动作:
1,给互斥锁解锁。(这就要求在调用这个函数之前要上锁)
 2,把调用线程投入睡眠。

一旦这个函数被唤醒后,那么在此函数返回前重新给互斥锁上锁。这也就决定了在接下来的程序里必须有解锁的步骤。

 

互斥锁和条件变量的属性:

我们可以通过设置属性来选择是一个进程中多个线程同步还是多个进程间的同步。

 

1
#include <pthread.h>
2
 
3
int
 
pthread_mutex_destroy(pthread_mutex_t *mutex);
4
int
 
pthread_mutex_init(pthread_mutex_t *restrict mutex,
5
       
const
 
pthread_mutexattr_t *restrict attr);
6
int
 
pthread_cond_destroy(pthread_cond_t *cond);
7
int
 
pthread_cond_init(pthread_cond_t *restrict cond,
8
       
const
 
pthread_condattr_t *restrict attr);
 

 

 互斥锁和条件变量的属性如同互斥锁和条件变量一样,也分为静态和动态分配及其初始化。

 

1
#include <pthread.h>
2
 
3
int
 
pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
4
int
 
pthread_mutexattr_init(pthread_mutexattr_t *attr);
5
int
 
pthread_condattr_destroy(pthread_condattr_t *attr);
6
int
 
pthread_condattr_init(pthread_condattr_t *attr);
 

至于前面讲到的更改互斥锁和条件变量的属性来达到切换是线程间同步还是进程间同步的问题是通过如下函数来操作的:

 

 

01
#include <pthread.h>
02
 
03
int
 
pthread_mutexattr_getpshared(
const
 
pthread_mutexattr_t
*
04
       
restrict attr, 
int
 
*restrict pshared);
05
int
 
pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,
06
       
int
 
pshared);
07
int
 
pthread_condattr_getpshared(
const
 
pthread_condattr_t
*restrict attr,
08
       
int
 
*restrict pshared);
09
int
 
pthread_condattr_setpshared(pthread_condattr_t *attr,
10
       
int
 
pshared);
 

pshared的值就是用来设置这个属性的值,它可以是PTHREAD_PROCESS_PRIVATE(线程间同步)或PTHREAD_PROCESS_SHARED(进程间同步)。

 

 

条件变量定时等待和广播:

用来唤醒阻塞在此条件变量上的所有线程。

 

1
#include <pthread.h>
2
 
3
int
 
pthread_cond_broadcast(pthread_cond_t *cond);
4
int
 
pthread_cond_timedwait(pthread_cond_t *restrict cond,
5
       
pthread_mutex_t *restrict mutex,
6
       
const
 
struct
 
timespec *restrict abstime);
7
int
 
pthread_cond_wait(pthread_cond_t *restrict cond,
8
       
pthread_mutex_t *restrict mutex);
 

这里要说明一点,对于第二个函数所提到的abstime,这是绝对时间,而不是我们一般索说到的相差时间,也就是说这个时间是指自UTC时间以来所流逝的秒数和纳秒数,这样就有个一个好处:如果函数因为有信号到来而过早返回了,那么这个函数可以在无需更改参数的情况下继续再次被调用。

 

 

小结:

互斥锁用于保护代码临界区,从而保证任何时刻只有一个线程或者进程在临界区执行。有时候一个线程获得某个互斥锁后,发现自己需要等待某个条件变为真,这样线程就可以等待在某个条件上。条件变量总是有一个互斥锁与之关联。

互斥锁和条件变量可以静态分配并静态初始化。它们也可以动态分配并要求动态地初始化它们。动态初始化允许我们指定进程间共享属性,从而允许在不同进程间共享某个互斥锁或条件变量,其前提是该互斥锁或条件变量必须存在在由这些进程共享的内存区。

LINUX环境下多线程编程肯定会遇到需要条件变量的情况,此时必然要使用pthread_cond_wait()函数。但这个函数的执行过程比较难于理解。

    pthread_cond_wait()的工作流程如下(以MAN中的EXAMPLE为例):

       Consider two shared variables x and y, protected by the mutex mut, and a condition vari-

       able cond that is to be signaled whenever x becomes greater than y.

              int x,y;

              pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;

              pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

       Waiting until x is greater than y is performed as follows:

              pthread_mutex_lock(&mut);

              while (x <= y) {

                      pthread_cond_wait(&cond, &mut);

              }

              /* operate on x and y */

              pthread_mutex_unlock(&mut);

       Modifications on x and y that may cause x to become greater than y should signal the con-

       dition if needed:

              pthread_mutex_lock(&mut);

              /* modify x and y */

              if (x > y) pthread_cond_broadcast(&cond);

              pthread_mutex_unlock(&mut);

     这个例子的意思是,两个线程要修改X和 Y的值,第一个线程当X<=Y时就挂起,直到X>Y时才继续执行(由第二个线程可能会修改X,Y的值,当X>Y时唤醒第一个线程),即 首先初始化一个普通互斥量mut和一个条件变量cond。之后分别在两个线程中分别执行如下函数体:

              pthread_mutex_lock(&mut);

              while (x <= y) {

                      pthread_cond_wait(&cond, &mut);

              }

              /* operate on x and y */

              pthread_mutex_unlock(&mut);
和:       pthread_mutex_lock(&mut);

              /* modify x and y */

              if (x > y) pthread_cond_signal(&cond);

              pthread_mutex_unlock(&mut); 

    其实函数的执行过程非常简单,在第一个线程执行到pthread_cond_wait(&cond,&mut)时,此时如果X<=Y,则此函数就将mut互斥量解锁 ,再将cond条件变量加锁 ,此时第一个线程挂起 (不占用任何CPU周期)。

    而在第二个线程中,本来因为mut被第一个线程锁住而阻塞,此时因为mut已经释放,所以可以获得锁mut,并且进行修改X和Y的值,在修改之后,一个IF语句判定是不是X>Y,如果是,则此时pthread_cond_signal()函数会唤醒第一个线程 ,并在下一句中释放互斥量mut。然后第一个线程开始从pthread_cond_wait()执行,首先要再次锁mut , 如果锁成功,再进行条件的判断 (至于为什么用WHILE,即在被唤醒之后还要再判断,后面有原因分析),如果满足条件,则被唤醒 进行处理,最后释放互斥量mut 。

    至于为什么在被唤醒之后还要再次进行条件判断(即为什么要使用while循环来判断条件),是因为可能有“惊群效应”。有人觉得此处既然是被唤醒的,肯定 是满足条件了,其实不然。如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时就必须要再次条件判断,以使只有一个线程进入临界区处理。 对此,转来一段:

引用下POSIX的RATIONALE: 

Condition Wait Semantics 

It is important to note that when pthread_cond_wait() and pthread_cond_timedwait() return without error, the associated predicate may still be false. Similarly, when pthread_cond_timedwait() returns with the timeout error, the associated predicate may be true
due to an unavoidable race between the expiration of the timeout and the predicate state change. 

The application needs to recheck the predicate on any return because it cannot be sure there is another thread waiting on the thread to handle the signal, and if there is not then the signal is lost. The burden is on the application to check the predicate. 

Some implementations, particularly on a multi-processor, may sometimes cause multiple threads to wake up when the condition variable is signaled simultaneously on different processors. 

In general, whenever a condition wait returns, the thread has to re-evaluate the predicate associated with the condition wait to determine whether it can safely proceed, should wait again, or should declare a timeout. A return from the wait does not imply that
the associated predicate is either true or false. 

It is thus recommended that a condition wait be enclosed in the equivalent of a "while loop" that checks the predicate. 

从上文可以看出: 

1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上 的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程. 

2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.

       其实说白了很简单,就是pthread_cond_signal()也可能唤醒多个线程,而如果你同时只允许一个线程访问的话,就必须要使用while来进行条件判断,以保证临界区内只有一个线程在处理。

pthread_cond_signal函数的作用是发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。

但使用pthread_cond_signal不会有“惊群现象”产生,他最多只给一个线程发信号。假如有多个线程正在阻塞等待着这个条件变量的话,那么是根据各等待线程优先级的高低确定哪个线程接收到信号开始继续执行。如果各线程优先级相同,则根据等待时间的长短来确定哪个线程获得信号。但无论如何一个pthread_cond_signal调用最多发信一次。

另外,互斥量的作用一般是用于对某个资源进行互斥性的存取,很多时候是用来保证操作是一个原子性的操作,是不可中断的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  signal