您的当前位置:首页正文

pthread_cond_wait()函数详解

来源:要发发知识网

原话,就是说pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)函数传入的参数mutex用于保护条件,因为我们在调用pthread_cond_wait时,如果条件不成立我们就进入阻塞,但是进入阻塞这个期间,如果条件变量改变了的话,那我们就漏掉了这个条件。因为这个线程还没有放到等待队列上,所以调用pthread_cond_wait前要先锁互斥量,即调用pthread_mutex_lock(),pthread_cond_wait在把线程放进阻塞队列后,自动对mutex进行解锁,使得其它线程可以获得加锁的权利。这样其它线程才能对临界资源进行访问并在适当的时候唤醒这个阻塞的进程。当pthread_cond_wait返回的时候又自动给mutex加锁。实际上边代码的加解锁过程如下:/************pthread_cond_wait()的使用方法**********/pthread_mutex_lock(&qlock);    /*lock*/pthread_cond_wait(&qready, &qlock); /*block-->unlock-->wait() return-->lock*/pthread_mutex_unlock(&qlock); /*unlock*//*****************************************************/#include#include#include#include#include#include#include#include#include#include#includevoid* testThreadPool(int *t);

pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;

int a = 0;

int main() {

int sock_fd, conn_fd;

int optval;

socklen_t cli_len;

struct sockaddr_in cli_addr, serv_addr;

sock_fd = socket(AF_INET, SOCK_STREAM, 0);

if (sock_fd < 0) {

printf("socket\n");

}

optval = 1;

if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *) &optval,

sizeof(int)) < 0) {

printf("setsockopt\n");

}

memset(&serv_addr, 0, sizeof(struct sockaddr_in));

serv_addr.sin_family = AF_INET;

serv_addr.sin_port = htons(4507);

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(sock_fd, (struct sockaddr *) &serv_addr,

sizeof(struct sockaddr_in)) < 0) {

printf("bind\n");

}

if (listen(sock_fd, 100) < 0) {

printf("listen\n");

}

cli_len = sizeof(struct sockaddr_in);

int t;

pthread_t * mythread;

mythread = (pthread_t*) malloc(100 * sizeof(pthread_t));

for (t = 0; t < 5; t++) {

int *i=(int*)malloc(sizeof(int));

*i=t;

if (pthread_create(&mythread[t], NULL, (void*)testThreadPool, (void*)i) != 0) {

printf("pthread_create");

}

}

while (1) {

conn_fd = accept(sock_fd, (struct sockaddr *) &cli_addr, &cli_len);

if (conn_fd < 0) {

printf("accept\n");

}

printf("accept a new client, ip:%s\n", inet_ntoa(cli_addr.sin_addr));

pthread_mutex_lock(&clifd_mutex);

a=conn_fd;

pthread_cond_signal(&clifd_cond);

pthread_mutex_unlock(&clifd_mutex);

}

return 0;

}

void* testThreadPool(int *t) {

printf("t is %d\n", *t);

for (;;) {

pthread_mutex_lock(&clifd_mutex);

pthread_cond_wait(&clifd_cond, &clifd_mutex);

printf("a is %d\n", a);

printf("t is %d\n", *t);

pthread_mutex_unlock(&clifd_mutex);

sleep(100);

}

return (void*) 0;

}

了解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。

首先,让我们考虑以下情况:线程为查看已链接列表而锁定了互斥对象,然而该列表恰巧是空的。这一特定线程什么也干不了 -- 其设计意图是从列表中除去节点,但是现在却没有节点。因此,它只能:

锁定互斥对象时,线程将调用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 调用相当复杂,因此我们每次只执行它的一个操作。

pthread_cond_wait() 所做的第一件事就是同时对互斥对象解锁(于是其它线程可以修改已链接列表),并等待条件 mycond 发生(这样当 pthread_cond_wait() 接收到另一个线程的“信号”时,它将苏醒)。现在互斥对象已被解锁,其它线程可以访问和修改已链接列表,可能还会添加项。 【要求解锁并阻塞是一个原子操作】

此时,pthread_cond_wait() 调用还未返回。对互斥对象解锁会立即发生,但等待条件 mycond 通常是一个阻塞操作,这意味着线程将睡眠,在它苏醒之前不会消耗 CPU 周期。这正是我们期待发生的情况。线程将一直睡眠,直到特定条件发生,在这期间不会发生任何浪费 CPU 时间的繁忙查询。从线程的角度来看,它只是在等待 pthread_cond_wait() 调用返回。

现在继续说明,假设另一个线程(称作“2 号线程”)锁定了 mymutex 并对已链接列表添加了一项。在对互斥对象解锁之后,2 号线程会立即调用函数 pthread_cond_broadcast(&mycond)。此操作之后,2 号线程将使所有等待 mycond 条件变量的线程立即苏醒。这意味着第一个线程(仍处于 pthread_cond_wait() 调用中)现在将苏醒。

现在,看一下第一个线程发生了什么。您可能会认为在 2 号线程调用 pthread_cond_broadcast(&mymutex) 之后,1 号线程的 pthread_cond_wait() 会立即返回。不是那样!实际上,pthread_cond_wait() 将执行最后一个操作:重新锁定 mymutex。一旦 pthread_cond_wait() 锁定了互斥对象,那么它将返回并允许 1 号线程继续执行。那时,它可以马上检查列表,查看它所感兴趣的更改。

停止并回顾!

那个过程非常复杂,因此让我们先来回顾一下。第一个线程首先调用:

pthread_mutex_lock(&mymutex);

然后,它检查了列表。没有找到感兴趣的东西,于是它调用:

pthread_cond_wait(&mycond, &mymutex);

然后,pthread_cond_wait() 调用在返回前执行许多操作:

pthread_mutex_unlock(&mymutex);

它对 mymutex 解锁,然后进入睡眠状态,等待 mycond 以接收 POSIX 线程“信号”。一旦接收到“信号”(加引号是因为我们并不是在讨论传统的 UNIX 信号,而是来自 pthread_cond_signal() 或 pthread_cond_broadcast() 调用的信号),它就会苏醒。但 pthread_cond_wait() 没有立即返回 -- 它还要做一件事:重新锁定 mutex:

pthread_mutex_lock(&mymutex);

pthread_cond_wait() 知道我们在查找 mymutex “背后”的变化,因此它继续操作,为我们锁定互斥对象,然后才返回。