【Linux开发】linux线程同步方式条件变量介绍及实例

1、利用线程间共享的全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为 true );等待条件,挂起线程直到其他线程触发条件。

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);   

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

int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_signal(pthread_cond_t *cond);

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

对于

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

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

一定要在mutex的锁定区域内使用

    如果要正确的使用pthread_mutex_lockpthread_mutex_unlock,请参考

pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex

    另外,posix1标准说,pthread_cond_signalpthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是说,可以在lockunlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。

说明:

(1)pthread_cond_wait 自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用 pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)

(2)互斥量的解锁和在条件变量上挂起都是自动进行的。因此,在条件变量被触发前,如果所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争——个线程预备等待一个条件变量,当它在真正进入等待之前,另一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用pthread_cond_wait函数(block)之间被发出,从而造成无限制的等待)。

(3)pthread_cond_timedwait 和 pthread_cond_wait 一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在abstime指定的时间内cond未触发,互斥量mutex被重新加锁,且pthread_cond_timedwait返回错误 ETIMEDOUTabstime 参数指定一个绝对时间,时间原点与 time gettimeofday 相同:abstime = 0 表示 19701100:00:00 GMT

(4)pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上等待的线程。

(5)条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal pthread_cond_boardcast 函数,可能导致调用线程死锁。

2、实例代码

#include
    #include
    #include "stdio.h"
    #include "stdlib.h"

    static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
    static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

    struct node
    {
         int n_number;
         struct node *n_next;
    } *head = NULL;

    /*[thread_func]*/
    static void cleanup_handler(void *arg)
    {
         printf("Cleanup handler of second thread.\n");
         free(arg);
         (void)pthread_mutex_unlock(&mtx);
    }

    static void *thread_func(void *arg)
    {
         struct node *p = NULL;
         pthread_cleanup_push(cleanup_handler, p);
         while (1)
         {
             //这个mutex主要是用来保证pthread_cond_wait的并发性
             pthread_mutex_lock(&mtx);
          
             pthread_cond_wait(&cond, &mtx);
             p = head;
             head = head->n_next;
             printf("Got %d from front of queue\n", p->n_number);
             free(p);

              pthread_mutex_unlock(&mtx); //临界区数据操作完毕,释放互斥锁
         }
         pthread_cleanup_pop(0);
         return 0;
    }

    int main(void)
    {
         pthread_t tid;
         int i;
         struct node *p;
         //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而
         //不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
         pthread_create(&tid, NULL, thread_func, NULL);
         //sleep(1);
         for (i = 0; i < 10; i++)
         {
             p = (struct node*)malloc(sizeof(struct node));
             p->n_number = i;
             pthread_mutex_lock(&mtx); //需要操作head这个临界资源,先加锁,
             p->n_next = head;
             head = p;
             pthread_cond_signal(&cond);
             pthread_mutex_unlock(&mtx); //解锁
             sleep(1);
         }
         printf("thread 1 wanna end the line.So cancel thread 2.\n");
         
         //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出
         //线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。
         pthread_cancel(tid);
         pthread_join(tid, NULL);
         printf("All done -- exiting\n");
         return 0;
    }

gcc -o con con.c -lpthread;./con

运行结果:

Got 1 from front of queue
Got 2 from front of queue
Got 3 from front of queue
Got 4 from front of queue
Got 5 from front of queue
Got 6 from front of queue
Got 7 from front of queue
Got 8 from front of queue
Got 9 from front of queue
thread 1 wanna end the line.So cancel thread 2.
Cleanup handler of second thread.
All done -- exiting

你可能感兴趣的:(Linux,Develop)