使用条件变量进行线程间的同步

什么是条件变量

先看一下APUE第三版对于条件变量的说明:

Condition variables are another synchronization mechanism available to threads. These synchronization objects provide a place for threads to rendezvous. When used with mutexes, condition variables allow threads to wait in a race-free way for arbitrary conditions to occur.
The condition itself is protected by a mutex. A thread must first lock the mutex to change the condition state. Other threads will not notice the change until they acquire the mutex, because the mutex must be locked to be able to evaluate the condition.

条件变量是另一种线程同步机制,它为线程间共享的对象提供了同步的方法。当条件变量配合互斥锁(Mutex)使用时,允许多个线程处在一种自由等待任意条件发生的状态。

条件变量自身由互斥锁(Mutex)保护。线程必须在修改条件状态之前先对其上锁,其他线程不会在获取锁之前被通知到其状态变化,因为只有获取到锁才可以计算条件。

条件变量的数据类型是pthread_cond_t,条件变量属性的类型是pthread_condattr_t,它们都包含在头文件中。

初始化和销毁

条件变量使用之前必须初始化,有两种方法:

  1. 对于静态的条件变量,可以用PTHREAD_COND_INITIALIZER来初始化。
  2. 用pthread_cond_init初始化动态分配的条件变量,pthread_condattr_t是用来设定其属性的变量,具体使用会在后面提到。

需要释放条件变量时,使用pthread_cond_destroy即可。

#include 

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);

使用条件变量进行线程间的同步

等待

#include 

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);

调用phread_cond_wait或pthread_cond_timewait(以下简称wait函数)可以使当前线程等待某一条件的发生。两者的区别在于后者可以指定等待时间。
调用wait函数时,系统使调用线程进入等待状态后释放锁(所以我们必须先加锁后调用wait函数)。在这一步操作中的检查条件和进入等待是原子操作,所以线程不会错过条件的变化。当wait函数返回时,mutex会再次被加锁

其中pthread_cond_timewait中用到的timespec结构定义如下:

struct timespec
{
    __time_t tv_sec;        /* Seconds.  */
    __syscall_slong_t tv_nsec;  /* Nanoseconds.  */
};

需要注意的是,timespec是一个绝对时间,所以在使用前我们需要先取得当前时间,再加上等待时间。例如下面这样:

#include 
#include 

void maketimeout(struct timespec *tsp, long minutes)
{
    struct timeval now;
    /* get the current time */
    gettimeofday(&now, NULL);
    tsp->tv_sec = now.tv_sec;
    tsp->tv_nsec = now.tv_usec * 1000; /* usec to nsec */
    /* add the offset to get timeout value */
    tsp->tv_sec += minutes * 60;
}

如果时间到了还没有等到条件变化,函数会对mutex重新加锁并返回一个ETIMEOUT的错误。

当wait函数返回成功时,需要重新检查条件,因为条件有可能已经被其他线程修改。

通知

#include 

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

当条件满足时,可以用这两个函数用来通知其他线程。

pthread_cond_signal会唤醒至少一个等待的线程,而pthread_cond_broadcast会唤醒所有等待的线程。必须注意的是:我们必须在状态发生变化之后再发送信号给其他线程

条件变量属性

条件变量的数据类型是pthread_cond_t,它主要有两种属性:

  1. 设置条件变量是否进程间共享。
  2. 调用pthread_cond_timewait时使用的计时方式,也就是使用clock_settime函数时的clockid_t类型参数。
#include 

int pthread_condattr_init(pthread_condattr_t *attr); 

int pthread_condattr_destroy(pthread_condattr_t *attr);

设置进程间共享属性:

int pthread_condattr_getpshared(const pthread_condattr_t * restrict attr, int *restrict pshared); 

int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);

设置时钟属性:

int pthread_condattr_getclock(const pthread_condattr_t * restrict attr, clockid_t *restrict clock_id); 

int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);

关于设置时钟属性的解释

pthread_cond_timewait函数用于在等待条件变量时提供超时功能,不过该函数的超时时间是一个绝对时间。默认使用系统时间,这意味着若修改系统时间,那么超时就不准确:有可能提前返回,也可能要几年才返回。这在某些情况下会导致bug,这时我们可以通过设置条件变量的时钟属性来避免这个问题。下面的例子展示了如何使用这个属性:

#include 
#include 
#include 
#include 
#include 

int main()
{
    pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
    pthread_condattr_t attr;
    pthread_cond_t cond;
    pthread_condattr_init(&attr);
    pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
    pthread_cond_init(&cond, &attr);
    struct timespec tv;
    pthread_mutex_lock(&m);
    do
    {
        clock_gettime(CLOCK_MONOTONIC, &tv);
        tv.tv_sec += 1;
        pthread_cond_timedwait(&cond,&m,&tv);
        printf("heart beat\n");
    } while(1);
    
    pthread_mutex_unlock(&m);
    return 0;
}
关于clockid_t的两种常用类型:
  1. CLOCK_REALTIME :默认类型,它记录的时间是按照系统的时间计算的,也就是说在计算中有人调整了系统时间,它也会受到影响。
  2. CLOCK_MONOTONIC : monotonic time的字面意思是单调时间,实际上它指的是系统启动以后流逝的时间。它一定是单调递增的,即使有人调整了系统时间,它也不会受到影响。

你可能感兴趣的:(使用条件变量进行线程间的同步)