• pthread 线程之条件变量


    简介:

    相关的函数如下:
    1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
    2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
    3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
    4 int pthread_cond_destroy(pthread_cond_t *cond);
    5 int pthread_cond_signal(pthread_cond_t *cond);
    6 int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
    简要说明:
    (1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL
    (2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真
    timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
    (3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
    (4)清除条件变量:destroy;无线程等待,否则返回EBUSY

    详细说明
    1. 初始化:
    条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
    静态方式使用PTHREAD_COND_INITIALIZER常量,如下:
    pthread_cond_t cond=PTHREAD_COND_INITIALIZER
    动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.
    #include <pthread.h>
    int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
    int pthread_cond_destroy(pthread_cond_t *cond);

    成功则返回0, 出错则返回错误编号.
    当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量; 非默认情况以后讨论.
    尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。
    阻塞在条件变量上pthread_cond_wait
    #include <pthread.h>
    int pthread_cond_wait(pthread_cond_t *cv,
    pthread_mutex_t *mutex);
    返回值:函数成功返回0;任何其他返回值都表示错误
    函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。
    被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,也可能在被信号中断后被唤醒。
    pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
    pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。
    一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。
    阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
    pthread_mutex_lock();
    while (condition_is_false)
    pthread_cond_wait();
    pthread_mutex_unlock();
    阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
    注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

    3.解除在条件变量上的阻塞pthread_cond_signal
    #include <pthread.h>
    int pthread_cond_signal(pthread_cond_t *cv);
    返回值:函数成功返回0;任何其他返回值都表示错误
    函数被用来释放被阻塞在指定条件变量上的一个线程。
    必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。
    唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。
    如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

    4.阻塞直到指定时间pthread_cond_timedwait
    #include <pthread.h>
    #include <time.h>
    int pthread_cond_timedwait(pthread_cond_t *cv,
    pthread_mutex_t *mp, const structtimespec * abstime);
    返回值:函数成功返回0;任何其他返回值都表示错误
    函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。
    注意:pthread_cond_timedwait函数也是退出点。
    超时时间参数是指一天中的某个时刻。使用举例:
    pthread_timestruc_t to;
    to.tv_sec = time(NULL) + TIMEOUT;
    to.tv_nsec = 0;
    超时返回的错误码是ETIMEDOUT。

    5.释放阻塞的所有线程pthread_cond_broadcast
    #include <pthread.h>
    int pthread_cond_broadcast(pthread_cond_t *cv);
    返回值:函数成功返回0;任何其他返回值都表示错误
    函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。
    由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

    6.释放条件变量pthread_cond_destroy
    #include <pthread.h>
    int pthread_cond_destroy(pthread_cond_t *cv);
    返回值:函数成功返回0;任何其他返回值都表示错误
    释放条件变量。
    注意:条件变量占用的空间并未被释放。

    7.唤醒丢失问题
    在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。
    唤醒丢失往往会在下面的情况下发生:
    一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
    另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
    没有线程正在处在阻塞等待的状态下。

    条件锁pthread_cond_t
    说明,
    等待线程
    1。使用pthread_cond_wait前要先加锁
    2。pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活
    3。pthread_cond_wait被激活后会再自动加锁

    激活线程:
    1。加锁(和等待线程用同一个锁)
    2。pthread_cond_signal发送信号
    3。解锁
    激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。


    其他
    pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点,因此,在该处等待的线程将立即重新运行,在重新锁定mutex后离开pthread_cond_wait(),然后执行取消动作。也就是说如果pthread_cond_wait()被取消,mutex是保持锁定状态的,因而需要定义退出回调函数来为其解锁。
    EXAMPLE
    Consider two shared variables x and y, protected by the mutex mut, and
    a condition variable 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 condition if needed:
    pthread_mutex_lock(&mut);
    /* modify x and y */
    if (x > y) pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mut);
    /* If it can be proved that at most one waiting thread needs to be waken
    up (for instance, if there are only two threads communicating through x
    and y), pthread_cond_signal can be used as a slightly more efficient
    alternative to pthread_cond_broadcast. In doubt, use
    pthread_cond_broadcast.
    To wait for x to becomes greater than y with a timeout of 5 seconds,
    do:
    */
    struct timeval now;
    struct timespec timeout;
    int retcode;
    pthread_mutex_lock(&mut);
    gettimeofday(&now);
    timeout.tv_sec = now.tv_sec + 5;
    timeout.tv_nsec = now.tv_usec * 1000;
    retcode = 0;
    while (x <= y && retcode != ETIMEDOUT) {
    retcode = pthread_cond_timedwait(&cond, &mut, &timeout);
    }
    if (retcode == ETIMEDOUT) {
    /* timeout occurred */
    } else {
    /* operate on x and y */
    }
    pthread_mutex_unlock(&mut);
    要想知道更详细,请看 man pthread_cond_wait

    ————————————————————————————————————————————————————
    经典的生产者/消费者的例证。
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #define MAX 5

    pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; /*初始化互斥锁*/
    pthread_cond_t=PTHREAD_CODE_INITIALIZER; /*初始化条件变量*/

    typedef struct{
    char buffer[MAX];
    int how_many;
    }BUFFER;

    BUFFER share={“”,0};
    char ch=’A’;/*初始化ch*/

    void *read_some(void *);
    void *write_some(void *);

    int main(void)
    {
    pthread_t t_read;
    pthread_t t_write;

    pthread_create(&t_read,NULL,read_some,(void *)NULL); /*创建进程t_a*/
    pthread_create(&t_write,NULL,write_some,(void *)NULL); /*创建进程t_b*/
    pthread_join(t_write,(void **)NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    exit(0);
    }

    void *read_some(void *junk)
    {
    int n=0;

    printf(“R %2d: starting/n”,pthread_self());

    while(ch!=’Z’)
    {
    pthread_mutex_lock(&lock_it);/*锁住互斥量*/
    if(share.how_many!=MAX)
    {

    share.buffer[share.how_many++]=ch++;/*把字母读入缓存*/
    printf(“R %2d:Got char[%c]/n”,pthread_self(),ch-1);/*打印读入字母*/
    if(share.how_many==MAX)
    {
    printf(“R %2d:signaling full/n”,pthread_self());
    pthread_cond_signal(&write_it);/*如果缓存中的字母到达了最大值就发送信号*/
    }
    pthread_mutex_unlock(&lock_it);/*解锁互斥量*/
    }
    sleep(1);
    printf(“R %2d:Exiting/n”,pthread_self());
    return NULL;
    }

    void *write_some(void *junk)
    {
    int i;
    int n=0;
    printf(“w %2d: starting/n”,pthread_self());

    while(ch!=’Z’)
    {
    pthread_mutex_lock(&lock_it);/*锁住互斥量*/
    printf(“/nW %2d:Waiting/n”,pthread_self());
    while(share.how_many!=MAX)/*如果缓存区字母不等于最大值就等待*/
    pthread_cond_wait(&write_it,&lock_it);
    printf(“W %2d:writing buffer/n”,pthread_self());
    for(i=0;share.buffer[i]&&share.how_many;++i,share.how_many--)
    putchar(share.buffer[i]); /*循环输出缓存区字母*/
    pthread_mutex_unlock(&lock_it);/*解锁互斥量*/
    }
    printf(“W %2d:exiting/n”,pthread_self());
    return NULL;
    }

    _______________________________________________________________________________________

    输入q后,需要等线程从sleep中醒来(由挂起状态变为运行状态),即最坏情况要等10s,线程才会被join。采用sleep的缺点:不能及时唤醒线程。
    采用pthread_cond_timedwait函数实现的如下:

    #include <stdio.h>

    #include <sys/time.h>;
    #include <unistd.h>;
    #include <pthread.h>;
    #include <errno.h>;

    pthread_t thread;
    pthread_cond_t cond;
    pthread_mutex_t mutex;
    bool flag = true;

    void * thr_fn(void * arg) {
    struct timeval now;
    struct timespec outtime;
    pthread_mutex_lock(&mutex);
    while (flag) {
    printf(".\n");
    gettimeofday(&now, NULL);
    outtime.tv_sec = now.tv_sec + 5;
    outtime.tv_nsec = now.tv_usec * 1000;
    pthread_cond_timedwait(&cond, &mutex, &outtime);
    }
    pthread_mutex_unlock(&mutex);
    printf("thread exit\n");
    }

    int main() {
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    if (0 != pthread_create(&thread, NULL, thr_fn, NULL)) {
    printf("error when create pthread,%d\n", errno);
    return 1;
    }
    char c ;
    while ((c = getchar()) != 'q');
    printf("Now terminate the thread!\n");
    flag = false;
    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    printf("Wait for thread to exit\n");
    pthread_join(thread, NULL);
    printf("Bye\n");
    return 0;
    }

    等待的时间通过abstime参数(绝对系统时间,过了该时刻就超时)指定,超时则返回ETIMEDOUT错误码。开始等待后,等待时间不受系统时钟改变的影响。

    尽管时间通过秒和纳秒指定,系统时间是毫秒粒度的。需要根据调度和优先级原因,设置的时间长度应该比预想的时间要多或者少点。可以通过使用系统时钟接口gettimeofday()获得timeval结构体。

    注: 为了可靠的使用条件变量和确保不忘记对条件变量的唤醒操作,应该采用一个bool变量和mutex变量同条件变量配合使用。如本文demo.

    ____________________________________________________________________________

    条件变量与互斥锁、信号量的区别
    1.互斥锁必须总是由给它上锁的线程解锁,信号量的挂出即不必由执行过它的等待操作的同一进程执行。一个线程可以等待某个给定信号灯,而另一个线程可以挂出该信号灯。
    2.互斥锁要么锁住,要么被解开(二值状态,类型二值信号量)。
    3.由于信号量有一个与之关联的状态(它的计数值),信号量挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么该信号将丢失。
    4.互斥锁是为了上锁而设计的,条件变量是为了等待而设计的,信号灯即可用于上锁,也可用于等待,因而可能导致更多的开销和更高的复杂性。

  • 相关阅读:
    SyntaxError: Non-ASCII character 'xe7' in file解决方法
    python实现微信打飞机游戏
    ubuntu 系统出错一览
    MVC的特点
    架构
    策略模式
    bin
    使用XSLT实现Word下载
    <a>标签的href属性
    call-template和apply-templates
  • 原文地址:https://www.cnblogs.com/bigben0123/p/2943080.html
Copyright © 2020-2023  润新知