UNIX环境高级编程-第11章- 线程 - 二

11.5 线程终止

        在进程中,若调用了函数 exit,_exit,或_Exit 时,则该进程会终止,同样,若进程中的线程调用这三个函数时也会使线程所在的进程终止。那么要是只是退出线程,而不终止线程所在的进程有什么办法?下面是在单线程模式下退出线程的三种方式(不会终止线程所在的进程):

(1)线程只是从启动例程中返回,返回值是线程的退出码;

(2)线程被同一进程的其他线程取消;

(3)线程调用 pthread_exit 函数;

/* 线程终止 */  
  
/* 
 * 函数功能:退出线程; 
 * 无返回值; 
 * 函数原型: 
 */  
#include <pthread.h>  
void pthread_exit(void *rval_ptr);  
/* 
 * 说明: 
 * rval_ptr是一个无类型指针,与传给启动例程的单个参数类似; 
 * 进程中的其他线程可以通过调用pthread_join函数访问到这个指针; 
 */  
  
/* 
 * 函数功能:获取其他已终止的线程的退出码; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
int pthread_join(pthread_t thread, void **rval_ptr);  
/* 
 * 说明: 
 * 调用pthread_join的线程将一直阻塞,直到thread指定的线程调用pthread_exit、从启动例程返回或被取消; 
 * 如果线程只是从启动例程返回,rval_ptr将包含返回码; 
 * 如果线程是被取消,由rval_ptr指向的内存单元设置为PTHREAD_CANCELED; 
 */  

        通常父进程需要调用wait函数族等待子进程,避免子进程成为僵尸进程。 在线程中为确保终止线程的资源对进程可用,即回收终止线程的资源,应该在每个线程结束时分离它们。一个没有被分离的线程终止时会保留其虚拟内存,包括它们的堆栈和其他系统资源。分离线程意味着通知系统不再需要此线程,允许系统将分配给它的资源回收。


        调用 pthread_join 函数将自动分离指定的线程,被分离的线程就再也不能被其他线程连接了,即恢复了系统资源。若线程已处于分离状态,调用pthread_join 会失败,将返回EINVAL。所以,如果多个线程需要知道某个特定的线程何时结束,则这些线程应该等待某个条件变量而不是调用pthread_join。

       下面针对这两个函数进行测试:

#include "apue.h"  
#include <pthread.h>  
  
static void *func1(void *arg);  
static void *func2(void *arg);  
  
int main(void)  
{  
    pthread_t tid1, tid2;  
    int err;  
    void *tret;  
  
    err = pthread_create(&tid1, NULL, func1, NULL);  
    if(err != 0)  
        err_quit("can't create thread 1: %s\n", strerror(err));  
    err = pthread_create(&tid2, NULL, func2, NULL);  
    if(err != 0)  
        err_quit("can't create thread 2: %s\n", strerror(err));  
  
    err = pthread_join(tid1,&tret);  
    if(err != 0)  
        err_quit("can't join with thread 1: %s\n", strerror(err));  
    printf("thread 1 exit code %d\n", (int)tret);  
  
    err = pthread_join(tid2,&tret);  
    if(err != 0)  
        err_quit("can't join with thread 2: %s\n", strerror(err));  
    printf("thread 2 exit code %d\n", (int)tret);  
  
    exit(0);  
}  
  
static void *func1(void *arg)  
{  
    printf("thread 1 returning\n");  
    return((void *)1);  
}  
  
static void *func2(void *arg)  
{  
    printf("thread 2 exiting\n");  
    pthread_exit((void*)2);  
}  

输出结果:

[root@localhost 11]# gcc 11-2.c -lpthread
[root@localhost 11]# ./a.out
thread 1 returning
thread 2 exiting
thread 1 exit code 1
thread 2 exit code 2

pthread_cancel 函数

          在同一进程中,线程可以请求取消同一进程的其他线程,pthread_cancel 函数可以实现该功能。

/* 
 * 函数功能:请求取消同一进程的其他线程; 
 * 返回值:若成功则返回0,否则返回错误; 
 * 函数原型: 
 */  
int pthread_cancel(pthread_t tid);  
/* 
 * 说明: 
 * 在默认情况下,该函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数, 
 * 但是线程可以选择忽略取消方式或控制取消方式; 
 * 注意:pthread_cancel并不等待线程终止,它仅仅提出请求; 
 */  

 pthread_cleanup_push 函数和 pthread_cleanup_pop函数

       在进程中可以调用 atexit 函数注册在 main 结束后调用清理程序的函数。同样,线程也可以安排它退出时需要调用的清理处理程序函数。可以把每个线程考虑为有一个活动的清理处理程序函数的栈。调用pthread_cleanup_push 将清理处理程序函数加到栈中,调用pthread_cleanup_pop 删除最近添加的清理处理程序函数。当线程退出时,从最近添加的清理处理程序函数开始,各个活动的清理处理程序函数都将被调用,当所有清理处理程序函数返回时,线程被终止。当pthread_cleanup_pop 中的参数 execute 为非 0 时,最近添加的清理处理程序函数也将被调用,然后删除。

/* 
 * 函数功能:线程清理处理程序; 
 * 无返回值; 
 * 函数原型: 
 */  
void pthread_cleanup_push(void(*rtn)(void *), void *arg);  
void pthread_cleanup_pop(int execute);  
/* 
 * 说明: 
 * 当线程执行以下动作时调用清理函数,调用参数为arg: 
 * (1)调用pthread_exit函数时; 
 * (2)响应取消请求时; 
 * (3)用非零execute参数调用pthread_cleanup_pop时; 
 * 如果参数execute设置为0,清理程序函数不被调用; 
 */  

测试程序:

#include <pthread.h>  
#include <sys/types.h>  
#include "apue.h"  
  
static void cleanup(void *arg);  //线程清理函数  
void *func1(void *arg);  
void *func2(void *arg);  
  
int main(void)  
{  
    pthread_t tid1;  
    pthread_t tid2;  
    int       err;  
    void   *tret;  
  
    err = pthread_create(&tid1, NULL, func1, (void*)1);  
    if(err != 0)  
        err_quit("can't create thread 1: %s\n", strerror(err));  
    err = pthread_create(&tid2, NULL, func2, (void*)1);  
    if(err != 0)  
        err_quit("can't create thread 2: %s\n", strerror(err));  
  
    err = pthread_join(tid1,&tret);  
    if(err != 0)  
        err_quit("can't join with thread 1: %s\n", strerror(err));  
    printf("thread 1 exit code %d\n", (int)tret);  
    err = pthread_join(tid2, &tret);  
    if(err != 0)  
        err_quit("can't join with thread 2: %s\n", strerror(err));  
    printf("thread 2 exit code %d\n",(int)tret);  
    exit(0);  
}  
  
static void cleanup(void *arg)  
{  
    printf("cleanup: %s\n", (char*)arg);  
}  
void *func1(void *arg)  
{  
   printf("thread 1 start.\n");  
   pthread_cleanup_push(cleanup,"thread 1 first handler");  //把清理处理程序函数入栈
   pthread_cleanup_push(cleanup,"thread 1 second handler");   //把清理处理程序函数入栈
   printf("thread 1 push complete.\n");  
   if(arg)  
    return ((void*)1);   //返回终止,将不会调用清理处理程序  函数,即不调用cleanup
   pthread_cleanup_pop(0);   //把清理处理程序函数出栈,且不调用
   pthread_cleanup_pop(0);  
   return ((void*)1);  
}  
void *func2(void *arg)  
{  
   printf("thread 2 start.\n");  
   pthread_cleanup_push(cleanup,"thread 2 first handler");   //把清理处理程序函数入栈
   pthread_cleanup_push(cleanup,"thread 2 second handler");   //把清理处理程序函数入栈
   printf("thread 2 push complete.\n");  
   if(arg)  
     pthread_exit((void*)2); //会调用清理处理程序  ,先调用栈顶的清理处理程序函数
   pthread_cleanup_pop(0);  
   pthread_cleanup_pop(0);  
   pthread_exit((void*)2);  
}  

输出结果:

[root@localhost 11]# ./a.out
thread 1 start.
thread 1 push complete.
thread 2 start.
thread 2 push complete.
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 1 exit code 1
thread 2 exit code 2

[root@localhost 11]# ./a.out
thread 1 start.
thread 1 push complete.
thread 2 start.
thread 2 push complete.
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 1 exit code 1
thread 2 exit code 2

可以发现,清理处理程序是按照与它们安装(入栈)时相反的顺序被调用的。

 

pthread_detach 函数

        在默认情况下,线程终止状态会保存到对该线程调用 pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能用 pthread_join 函数等待它的终止状态,对分离状态的线程进行 pthread_join 的调用会产生失败,返回 EINVAL。pthread_detach 调用可以用于线程进入分离状态。如果不想等待创建的某个线程,而且知道不再需要控制它,就可以调用 pthread_detach 来分离它。线程可以分离自己,任何知道其ID的其他线程也可以随时分离它。

/* 
 * 函数功能:使线程处于分离状态; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
int pthread_detach(pthread_t tid);  

测试程序:

#include <pthread.h>  
#include "apue.h"  
void *thr_fn(void *arg)  
{  
        printf("thread start ...\n");  
        printf("thread exiting...\n");  
        pthread_exit((void *)1);  
}  
  
int main(void)  
{  
        pthread_t tid1;  
        int err;  
        void* tret;  
  
        err = pthread_create(&tid1, NULL, thr_fn, (void*)1);  
        if(err != 0)  
            err_quit("pthread_create error: %s\n", strerror(err));  
  
        err = pthread_detach(tid1);  
        if(err != 0)  
            err_quit("pthread_detach error: %s\n", strerror(err));  
  
        err = pthread_join(tid1,&tret);  
        if(err != 0)  
            err_quit("pthread_join error: %s\n", strerror(err));  
        printf("thread 1 exit code:%d\n",(int)tret);  
        exit(0);  
}  

输出结果:

thread start ...  
thread exiting...  
pthread_join error: Invalid argument  

因为已经使用 pthread_detach 对线程进行分离,第二次调用 pthread_join 对线程进行分离时会返回 EINVAL。

 

11.6 线程同步

当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。当多个线程对可修改变量进行访问时,就会出现变量的一致性问题,这时就会涉及到线程同步的问题。

互斥量

        可以通过使用 pthread 的互斥接口保护数据,确保同一时间只有一个线程访问数据。互斥量本质上就是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成运行状态第一个变为运行状态的线程可以对互斥量进行加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前执行。

        互斥变量使用 pthread_mutex_t 数据类型来表示,在使用互斥量以前,必须先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init 函数进行初始化。如果动态地分配互斥量,那么在释放内存前需要调用pthread_mutex_destroy。

 

/* 互斥量 */  
/* 
 * 函数功能:初始化互斥变量; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
#include <pthread.h>  
  
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);  
int pthread_mutex_destroy(pthread_mutex_t *mutex);  
/* 
 * 说明: 
 * 若attr为NULL,表示初始化互斥量为默认属性; 
 */  
  
/* 
 * 函数功能:对互斥量进行加、解锁; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
int pthread_mutex_lock(pthread_mutex_t *mutex);//对互斥量进行加锁,线程被阻塞;  
int pthread_mutex_trylock(pthread_mutex_t *mutex);//对互斥变量加锁,但线程不阻塞;  
int pthread_mutex_unlock(pthread_mutex_t *mutex);//对互斥量进行解锁;  
/* 说明: 
 * 调用pthread_mutex_lock对互斥变量进行加锁,若互斥变量已经上锁,则调用线程会被阻塞直到互斥量解锁; 
 * 调用pthread_mutex_unlock对互斥量进行解锁; 
 * 调用pthread_mutex_trylock对互斥量进行加锁,不会出现阻塞,否则加锁失败,返回EBUSY。 
 */  

测试程序:

#include "threadmutex.h"  
#include "apue.h"  
#include <pthread.h>  
#include <sys/types.h>  
  
static void *thread_fun1(void *arg);  
static void *thread_fun2(void *arg);  
  
int main(void)  
{  
    pthread_t tid1, tid2;  
    int err;  
    void *ret;  
    struct foo *obj;  
    obj = foo_alloc();  
  
    err = pthread_create(&tid1, NULL, thread_fun1, (void*)obj);  
    if(err != 0)  
        err_quit("pthread_create error: %s\n", strerror(err));  
  
    err = pthread_create(&tid2, NULL, thread_fun2, (void*)obj);  
    if(err != 0)  
        err_quit("pthread_create error: %s\n", strerror(err));  
  
    pthread_join(tid1, &ret);  
    printf("thread 1 exit code is: %d\n", (int)ret);  
    pthread_join(tid2, &ret);  
    printf("thread 2 exit code is: %d\n", (int)ret);  
  
    exit(0);  
}  
  
static void *thread_fun1(void *arg)  
{  
    struct foo *fp = (struct foo *)arg;  
    printf("thread 1 starting...\n");  
    foo_release(fp);  
    printf("thread 1 returning...\n");  
    pthread_exit((void*)1);  
}  
  
static void *thread_fun2(void *arg)  
{  
    struct foo *fp = (struct foo *)arg;  
    printf("thread 2 starting...\n");  
    foo_hold(fp);  
    foo_hold(fp);  
    printf("thread 2 returning...\n");  
    pthread_exit((void*)2);  
}  

其中threadmutex.h头文件内容如下:

#ifndef THREADMUTEX_H  
#define THREADMUTEX_H  
#include <stdlib.h>  
#include <pthread.h>  
#include <stdio.h>  
  
struct foo{  
    int f_count;  
#ifndef lock  
    pthread_mutex_t f_lock;//定义互斥锁  
#endif  
};  
  
struct foo *foo_alloc(void)  
{  
    struct foo *fp;  
    if((fp = (struct foo *)malloc(sizeof(struct foo))) != NULL)  
    {  
        fp->f_count = 1;  
#ifndef lock  
        if(pthread_mutex_init(&fp->f_lock, NULL) != 0)//进行初始化  
        {  
            free(fp);  
            return(NULL);  
        }  
#endif  
    }  
    return(fp);  
}  
  
void foo_hold(struct foo *fp)  
{  
#ifndef lock  
    pthread_mutex_lock(&fp->f_lock);  
#endif  
    fp->f_count++;  
    printf("f_count = %d\n", fp->f_count);  
#ifndef lock  
    pthread_mutex_unlock(&fp->f_lock);  
#endif  
}  
  
void foo_release(struct foo *fp)  
{  
#ifndef lock  
    pthread_mutex_lock(&fp->f_lock);  
#endif  
    fp->f_count--;  
    printf("f_count = %d\n", fp->f_count);  
#ifndef lock  
    if(fp->f_count == 0)  
    {  
        pthread_mutex_unlock(&fp->f_lock);  
        pthread_mutex_destroy(&fp->f_lock);  
        free(fp);  
    }  
    else  
        pthread_mutex_unlock(&fp->f_lock);  
#endif  
}  
#endif  

输出结果:

$ ./mutex   
thread 1 starting...  
thread 2 starting...  
f_count = 0  
thread 1 returning...  
f_count = 1  
f_count = 2  
thread 2 returning...  
thread 1 exit code is: 1  
thread 2 exit code is: 2  
$ ./mutex   
thread 2 starting...  
f_count = 2  
f_count = 3  
thread 2 returning...  
thread 1 starting...  
f_count = 2  
thread 1 returning...  
thread 1 exit code is: 1  
thread 2 exit code is: 2  

从输出结果可以知道,两个线程之间的执行有一定的顺序,即两个线程对数据的修改具有同步性。

若没有对这两个线程加上互斥锁,则可能会输出这样的结果(即两个线程对数据修改不同步):

$./mutex  
thread 2 starting...  
thread 1 starting...  
f_count = 2  
f_count = 2  
thread 2 returning...  
f_count = 1  
thread 1 returning...  
thread 1 exit code is: 1  
thread 2 exit code is: 2  


避免死锁

        若线程试图对同一个互斥量加锁两次,那么自身会陷入死锁状态,使用互斥量时,还有其他更不明显的方式也能产生死锁,例如,程序中使用多个互斥量,如果允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,这时就会发生死锁。因为两个线程都在相互请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是就产生死锁。

避免死锁的方法:

控制互斥量加锁的顺序。

使用“试加锁与回退”:在使用 pthread_mutex_lock() 锁住第一把锁的时候,其余的锁使用pthread_mutex_trylock()来锁定,如果返回EBUSY,则释放前面占有的所有的锁,过段时间之后再重新尝试。

读写锁

        互斥量只有两种状态:加锁和不加锁,并且同一时刻只有一个线程对其加锁。读写锁有三种状态:读模式下加锁、写模式下加锁、不加锁;一次只有一个线程占用写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

         读写锁非常适合于对数据结构读的次数远大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为当前只有一个线程可以在写模式下拥有这个锁。当读写锁在读模式下时,只要线程获取了读模式下的读写锁,该锁所保护的数据结构可以被多个获得读写模式锁的线程读取。

        读写锁是一种共享-独占锁,当读写锁以读模式加锁时,它是以共享模式锁住;当以写模式加锁时,它以独占模式锁住;读写锁初始化和加解锁如下:

/* 读写锁 */  
/* 
 * 函数功能:初始化读写锁; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
#include <pthread.h>  
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);  
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);  
/* 
 * 通过调用pthread_rwlock_init进程初始化,attr为NULL表示读写锁为默认的属性。 
 * 在释放读写锁占用的内存之前,需要调用pthread_rwlock_destroy做清理工作。 
 */  
  
/* 
 * 函数功能:对读写锁进行加、解锁; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//在读模式下对读写锁进行加锁;  
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//在写模式下对读写锁进行加锁;  
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//进行解锁;  

测试程序:

#include "threadrwlock.h"  
#include "apue.h"  
#include <pthread.h>  
#include <sys/types.h>  
  
static void *thread_fun1(void *arg);  
static void *thread_fun2(void *arg);  
static void *thread_fun3(void *arg);  
  
int main(void)  
{  
    pthread_t tid1, tid2, tid3;  
    int err;  
    void *ret;  
    struct foo *obj;  
    obj = foo_alloc();  
  
    err = pthread_create(&tid1, NULL, thread_fun1, (void*)obj);  
    if(err != 0)  
        err_quit("pthread_create error: %s\n", strerror(err));  
  
    err = pthread_create(&tid2, NULL, thread_fun2, (void*)obj);  
    if(err != 0)  
        err_quit("pthread_create error: %s\n", strerror(err));  
  
    err = pthread_create(&tid3, NULL, thread_fun3, (void*)obj);  
    if(err != 0)  
        err_quit("pthread_create error: %s\n", strerror(err));  
  
    pthread_join(tid1, &ret);  
    printf("thread 1 exit code is: %d\n", (int)ret);  
    pthread_join(tid2, &ret);  
    printf("thread 2 exit code is: %d\n", (int)ret);  
    pthread_join(tid3, &ret);  
    printf("thread 3 exit code is: %d\n", (int)ret);  
  
    exit(0);  
}  
  
static void *thread_fun1(void *arg)  
{  
    struct foo *fp = (struct foo *)arg;  
    printf("thread 1 starting...\n");  
    foo_release(fp);  
    printf("thread 1 returning...\n");  
    pthread_exit((void*)1);  
}  
  
static void *thread_fun2(void *arg)  
{  
    struct foo *fp = (struct foo *)arg;  
    printf("thread 2 starting...\n");  
    foo_hold(fp);  
    foo_hold(fp);  
    printf("thread 2 returning...\n");  
    pthread_exit((void*)2);  
}  
static void *thread_fun3(void *arg)  
{  
   int count;  
    struct foo *fp = (struct foo *)arg;  
    printf("thread 3 starting...\n");  
    count = foo_find(fp);  
    printf("thread 3 returning...\n");  
    printf("f_count = %d\n",count);  
    pthread_exit((void*)3);  
}  

其中threadrwlock.h的内容如下:

#ifndef THREADRWLOCK_H  
#define THREADRWLOCK_H  
#include <stdlib.h>  
#include <pthread.h>  
#include <stdio.h>  
  
//#define lock 1  
struct foo{  
    int f_count;  
#ifndef lock  
    pthread_rwlock_t f_lock;//定义读写锁  
#endif  
};  
  
struct foo *foo_alloc(void)  
{  
    struct foo *fp;  
    if((fp = (struct foo *)malloc(sizeof(struct foo))) != NULL)  
    {  
        fp->f_count = 1;  
#ifndef lock  
        if(pthread_rwlock_init(&fp->f_lock, NULL) != 0)//使用读写锁之前进行初始化  
        {  
            free(fp);  
            return(NULL);  
        }  
#endif  
    }  
    return(fp);  
}  
  
void foo_hold(struct foo *fp)  
{  
#ifndef lock  
    pthread_rwlock_wrlock(&fp->f_lock);  
#endif  
    fp->f_count++;  
    printf("f_count = %d\n", fp->f_count);  
#ifndef lock  
    pthread_rwlock_unlock(&fp->f_lock);  
#endif  
}  
  
void foo_release(struct foo *fp)  
{  
#ifndef lock  
    pthread_rwlock_wrlock(&fp->f_lock);  
#endif  
    fp->f_count--;  
    printf("f_count = %d\n", fp->f_count);  
#ifdef lock  
    if(fp->f_count == 0)  
        free(fp);  
#endif  
#ifndef lock  
    if(fp->f_count == 0)  
    {  
        pthread_rwlock_unlock(&fp->f_lock);  
        pthread_rwlock_destroy(&fp->f_lock);  
        free(fp);  
    }  
    else  
        pthread_rwlock_unlock(&fp->f_lock);  
#endif  
}  
  
#ifndef lock  
int foo_find(struct foo *fp)  
{  
    pthread_rwlock_rdlock(&fp->f_lock);  
    int count = fp->f_count;  
    pthread_rwlock_unlock(&fp->f_lock);  
    return count;  
}  
#endif  
#endif  

线程1和线程2以写模式对读写锁加锁,线程3以读模式对读写锁进行加锁。输出结果:

$./rwlock  
thread 2 starting...  
f_count = 2  
f_count = 3  
thread 2 returning...  
thread 1 starting...  
thread 3 starting...  
f_count = 2  
thread 1 returning...  
thread 3 returning...  
f_count = 2  
thread 1 exit code is: 1  
thread 2 exit code is: 2  
thread 3 exit code is: 3  

条件变量

        条件变量给多个线程提供了一个会合的机会,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生,条件本身是由互斥量保护。线程在改变条件状态前必须先锁住互斥量,条件变量允许线程等待特定条件发生。条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。条件变量类型为pthread_cond_t,使用前必须进行初始化。可以有两种初始化方式:把常量 PTHREAD_COND_INITIALIZER 赋给静态分配的条件变量,对于动态分配的条件变量,可以使用 pthread_cond_init 进行初始化。操作函数如下: 

/* 条件变量 */  
/* 
 * 函数功能:初始化条件变量; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
#include <pthread.h>  
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);  
int pthread_cond_destroy(pthread_cond_t *cond);  
  
/* 
 * 函数功能:等待条件变量变为真; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex,  
            const struct timespec *timeout);  
/* 
 * 说明: 
 * 传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数; 
 * 函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两操作是原子操作; 
 * 这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样就不会错过条件变化; 
 * pthread_cond_wait返回时,互斥量再次被锁住; 
 */  
  
/* 
 * 函数功能:唤醒等待条件的线程; 
 * 返回值:若成功则返回0,否则返回错误编码; 
 * 函数原型: 
 */  
int pthread_cond_signal(pthread_cond_t *cond);//唤醒等待该条件的某个线程;  
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒等待该条件的所有线程;  

测试程序:

#include "threadcond.h"  
#include "apue.h"  
#include <pthread.h>  
#include <sys/types.h>  
  
static void *thread_fun1(void *arg);  
static void *thread_fun2(void *arg);  
  
int main(void)  
{  
    pthread_t tid1, tid2;  
    int err;  
    void *ret;  
    struct foo *obj;  
    obj = foo_alloc();  
  
    err = pthread_create(&tid1, NULL, thread_fun1, (void*)obj);  
    if(err != 0)  
        err_quit("pthread_create error: %s\n", strerror(err));  
  
    err = pthread_create(&tid2, NULL, thread_fun2, (void*)obj);  
    if(err != 0)  
        err_quit("pthread_create error: %s\n", strerror(err));  
  
    pthread_join(tid1, &ret);  
    printf("thread 1 exit code is: %d\n", (int)ret);  
    pthread_join(tid2, &ret);  
    printf("thread 2 exit code is: %d\n", (int)ret);  
  
    exit(0);  
}  
  
static void *thread_fun1(void *arg)  
{  
    struct foo *fp = (struct foo *)arg;  
    printf("thread 1 starting...\n");  
    foo_release(fp);  
    printf("thread 1 returning...\n");  
    pthread_exit((void*)1);  
}  
  
static void *thread_fun2(void *arg)  
{  
    struct foo *fp = (struct foo *)arg;  
    printf("thread 2 starting...\n");  
    foo_hold(fp);  
    foo_hold(fp);  
    printf("thread 2 returning...\n");  
    pthread_exit((void*)2);  
}  

其中threadcond.h的内容为:

#ifndef THREADCOND_H  
#define THREADCOND_H  
#include <stdlib.h>  
#include <pthread.h>  
#include <stdio.h>  
  
//#define lock 1  
struct foo{  
    int f_count;  
#ifndef lock  
    pthread_mutex_t f_lock;//定义互斥锁  
    pthread_cond_t f_cond;//条件变量  
#endif  
};  
  
struct foo *foo_alloc(void)  
{  
    struct foo *fp;  
    if((fp = (struct foo *)malloc(sizeof(struct foo))) != NULL)  
    {  
        fp->f_count = 0;  
#ifndef lock  
        if(pthread_cond_init(&fp->f_cond,NULL) != 0 || pthread_mutex_init(&fp->f_lock, NULL) != 0)  
        {  
            free(fp);  
            return(NULL);  
        }  
#endif  
    }  
    return(fp);  
}  
  
void foo_hold(struct foo *fp)  
{  
#ifndef lock  
    pthread_mutex_lock(&fp->f_lock);  
#endif  
    fp->f_count++;  
    if(fp->f_count != 0)  
        pthread_cond_signal(&fp->f_cond);  
    printf("f_count = %d\n", fp->f_count);  
#ifndef lock  
    pthread_mutex_unlock(&fp->f_lock);  
#endif  
}  
  
void foo_release(struct foo *fp)  
{  
#ifndef lock  
    pthread_mutex_lock(&fp->f_lock);  
#endif  
    while(fp->f_count == 0)  
        pthread_cond_wait(&fp->f_cond, &fp->f_lock);  
    fp->f_count--;  
    printf("f_count = %d\n", fp->f_count);  
#ifdef lock  
    if(fp->f_count == 0)  
        free(fp);  
#endif  
#ifndef lock  
    if(fp->f_count == 0)  
    {  
        pthread_mutex_unlock(&fp->f_lock);  
        pthread_mutex_destroy(&fp->f_lock);  
        free(fp);  
    }  
    else  
        pthread_mutex_unlock(&fp->f_lock);  
#endif  
}  
  
#ifndef lock  
int foo_find(struct foo *fp)  
{  
    pthread_mutex_lock(&fp->f_lock);  
    int count = fp->f_count;  
    pthread_mutex_unlock(&fp->f_lock);  
    return count;  
}  
#endif  
#endif  

输出结果:

thread 1 starting...  
thread 2 starting...  
f_count = 1  
f_count = 2  
thread 2 returning...  
f_count = 1  
thread 1 returning...  
thread 1 exit code is: 1  
thread 2 exit code is: 2  

/*  等待条件变为真 */
 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); 
       
 /* 唤醒一个等待条件的线程.  */
 int pthread_cond_signal(pthread_cond_t *cond); 
       
 /* 唤醒等待该条件的所有线程 */
 int pthread_cond_broadcast(pthread_cond_t *cond);

(1)pthread_cond_wait()函数用于等待条件被触发。该函数传入两个参数,一个条件变量一个互斥量,函数将条件变量和互斥量进行关联,互斥量对该条件进行保护,传入的互斥量必须是已经锁住的。调用pthread_cond_wait()函数后,会原子的执行以下两个动作:

  (1) 将调用线程放到等待条件的线程列表上,即进入睡眠;
   (2)对互斥量进行解锁;

    由于这两个操作是原子操作,这样就关闭了条件检查和线程进入睡眠等待条件改变这两个操作之间的时间通道,这样就不会错过任何条件的变化。

当pthread_cond_wait()返回后,互斥量会再次被锁住。

(2)pthread_cond_signal() & pthread_cond_broadcast()

    这两个函数都是用于向等待条件的线程发送唤醒信号,pthread_cond_signal()函数只会唤醒等待该条件的某个线程,pthread_cond_broadcast()会广播条件状态的改变,以唤醒等待该条件的所有线程。例如多个线程只读共享资源,这是可以将它们都唤醒。

    这里要注意的是:一定要在改变条件状态后,再给线程发送信号。

    考虑条件变量信号单播发送和广播发送的一种候选方式是坚持使用广播发送。只有在等待者代码编写确切,只有一个等待者需要唤醒,且唤醒哪个线程无所谓,那么此时为这种情况使用单播,所以其他情况下都必须使用广播发送。

    唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值