pthread_cond_wait

首先你必须知道pthread_cleanup_push与pthread_cleanup_pop的目的(作用)是什么。

比如thread1:
执行
pthread_mutex_lock(&mutex);

//一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接
sock = accept(......);            //这里是随便找的一个可以阻塞的接口

pthread_mutex_unlock(&mutex);
这个例子中,如果线程1执行accept时,线程会阻塞(也就是等在那里,有客户端连接的时候才返回,或则出现其他故障),线程等待中......

这时候线程2发现线程1等了很久,不赖烦了,他想关掉线程1,于是调用pthread_cancel()或者类似函数,请求线程1立即退出。

这时候线程1仍然在accept等待中,当它收到线程2的cancel信号后,就会从accept中退出,然后终止线程,注意这个时候线程1还没有执行:
pthread_mutex_unlock(&mutex);
也就是说锁资源没有释放,这回造成其他线程的死锁问题。

所以必须在线程接收到cancel后用一种方法来保证异常退出(也就是线程没达到终点)时可以做清理工作(主要是解锁方面),pthread_cleanup_push与pthread_cleanup_pop就是这样的。

pthread_cleanup_push(some_clean_func,...)
pthread_mutex_lock(&mutex);

//一些会阻塞程序运行的调用,比如套接字的accept,等待客户连接
sock = accept(......);            //这里是随便找的一个可以阻塞的接口

pthread_mutex_unlock(&mutex);
pthread_cleanup_pop(0);
return NULL;
上面的代码,如果accept被cancel后线程退出,会自动调用some_clean_func函数,在这个函数中你可以释放锁资源。如果 accept没有被cancel,那么线程继续执行,当pthread_mutex_unlock(&mutex);表示线程自己正确的释放资源 了,而执行pthread_cleanup_pop(0);也就是取消掉前面的some_clean_func函数。接着return线程就正确的结束 了。

不晓得你明白没,通俗点就是:
pthread_cleanup_push注册一个回调函数,如果你的线程在对应的pthread_cleanup_pop之前异常退出(return是 正常退出,其他是异常),那么系统就会执行这个回调函数(回调函数要做什么你自己决定)。但是如果在pthread_cleanup_pop之前没有异常 退出,pthread_cleanup_pop就把对应的回调函数取消了,

关于取消点的解释:

比如你执行:
        printf("thread sleep\n");
        sleep(10);
        printf("thread wake...\n");
在sleep函数中,线程睡眠,结果收到cancel信号,这时候线程从sleep中醒来,但是线程不会立刻退出。这是应为pthread与C库方面的原因(具体是啥我也不清楚),pthread的建议是,如果一个函数是阻塞的,那么你必须在这个函数前后建立取消点,比如:
        printf("thread sleep\n");
        pthread_testcancel();
        sleep(10);
        pthread_testcancel();
        printf("thread wake...\n");
这样,就添加了两个取消掉。在执行到pthread_testcancel的位置时,线程才可能响应cancel退出进程。

额外的知识:
对于cancel信号,线程有两种方法: 忽略,和响应。默认是响应
接收到cancel信号,线程有两种处理类型: 立即响应 和 延迟响应(在最近的取消点响应),默认是延迟响应

 

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <malloc.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;

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的并发性
        printf("subthread to lock()....\n");
        pthread_mutex_lock(&mtx);          
        printf("subthread get lock\n");
        while (head == NULL)   {
          //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何这里要有一个while (head == NULL)呢?因
          //为pthread_cond_wait里的线程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。这个时候,应该让线程继续进入pthread_cond_wait

          printf("subthread to wait()...\n");
          //pthread_cond_wait()内部会解锁,然后等待条件变量被其它线程激活,它被激活后会自动加锁
          // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,然后阻塞在等待对列里休眠,直到再次被唤醒
          //(大多数情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
          pthread_cond_wait(&cond, &mtx);        
          printf("subthread is notified\n");
          //用这个流程是比较清楚的/*block-->unlock-->wait() return-->lock*/
        }

        p = head;
        head = head->n_next;//消费一个节点
        printf("Got %d from front of queue\n", p->n_number);
        free(p);
        //临界区数据操作完毕,释放互斥锁
        printf("subthread unlock()\n");
        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);   //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
    
    for (i = 0; i < 10; i++) {
        p = malloc(sizeof(struct node));
        p->n_number = i;
        printf("main thread to lock()...\n");
        pthread_mutex_lock(&mtx);             //需要操作head这个临界资源,先加锁,
        p->n_next = head;
        head = p;
        printf("main thread to signal()\n");
        pthread_cond_signal(&cond);   // 生产者生产好后,通知消费者
        printf("main thread to unlock()\n");
        pthread_mutex_unlock(&mtx);           //解锁
        sleep(4);
    }
    printf("thread 1 wanna end the line.So cancel thread 2.\n");
    //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出线程,
    //而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。关于取消点的信息,有兴趣可以google,这里不多说了
    pthread_cancel(tid);             
    pthread_join(tid, NULL);
    printf("All done -- exiting\n");
    return 0;
}

 muhe221@muhe:~/Downloads$ ./hehe.o
subthread to lock()....
subthread get lock
subthread to wait()...

main thread to lock()...
main thread to signal()
main thread to unlock()
subthread is notified
Got 0 from front of queue
subthread unlock()
subthread to lock()....
subthread get lock
subthread to wait()...

main thread to lock()...
main thread to signal()
main thread to unlock()
subthread is notified
Got 1 from front of queue
subthread unlock()
subthread to lock()....
subthread get lock
subthread to wait()...

main thread to lock()...
main thread to signal()
main thread to unlock()
subthread is notified
Got 2 from front of queue
subthread unlock()
subthread to lock()....
subthread get lock
subthread to wait()...

posted @ 2015-09-30 10:38  牧 天  阅读(235)  评论(0)    收藏  举报