Unix高级IO之多线程编程 ——pthread解析

来源:互联网 发布:mac pro如何重装系统 编辑:程序博客网 时间:2024/05/17 03:23

线程


线程包含了表示进程内执行环境必须的信息,其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。

进程的所以信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。

标识:每个线程都有一个线程ID,且该ID只在它所属的进程环境中有效。

获得自身的线程ID:

  1. #include <pthread.h>
  2. pthread_t pthread_self(void);   #返回值为:调用线程的线程ID

创建线程:

  1. #include <pthread.h>
  2. int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg); #返回值:若成功返回0,否则返回错误编码

参数说明:

第一个参数tidp,为pthread_t数据类型的指针,pthread_create成功返回后,tidp指向的内存单元被设置为新创建线程的线程ID

第二个参数attr,为pthread_attr_t数据类型指针,用于定制各种不同的线程属性,设置为NULL,则表示为设置成默认属性

第三个参数为start_rtn函数,线程由该函数的地址开始运行

第四个参数为start_rtn函数所需参数,若所需参数不止一个,则需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入

注:线程创建时并不能保证哪个线程先运行,是新创建的线程还是调用线程;另外pthread函数在调用失败时会返回错误码

终止线程:

如果进程中任一线程调用了exit,_Exit或者_exit,那么整个进程就好终止。

退出线程方法:

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

2),线程可以被同一进程中的其他线程取消

3),线程调用pthread_exit

  1. #include <pthread.h>
  2. void pthread_exit(void *rval_ptr);

rval_ptr是一个无类型指针,进程中的其他线程可以通过调用pthread_join函数访问到这个指针。

  1. #include <pthread.h>
  2. int pthread_join(pthread_t thread, void **rval_ptr); #返回值:若成功则返回0,失败返回错误编号

调用pthread_join函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者取消。

当线程取消时,pthread_join返回,会根据情况把取消的线程的返回值置于rval_ptr中,若对线程的返回值不感兴趣,可以把rval_ptr置为NULL。

取消同一进程中的其他线程:

  1. #include <pthread.h>
  2. void pthread_cancel(pthread_t tid);

注意,该方法并不等待线程终止,它仅仅是提出请求。

线程清理处理程序

线程可以安排它退出时需要调用的函数,这就是线程清理处理程序要做的工作。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。

  1. #include <pthread.h>
  2. void pthread_cleanup_push(void (*rtn)(void *), void *arg);
  3. void pthread_cleanup_pop(int execute); #无论哪种情况,该函数都会删除上次pthread_cleanup_push调用建立的清除处理程序

示例:

  1. #include "apue.h"
  2. #include <pthread.h>
  3. void cleanup(void *arg)
  4. {
  5.    printf("cleanup: %s\n", (char*)arg);
  6. void * thr_fn1(void * arg)
  7. {
  8.    printf("thread 1 start\n");
  9.    pthread_cleanup_push(cleanup, "thread 1 first handler");
  10.    pthread_cleanup_push(cleanup, "thread 1 second handler");
  11.    printf("thread 1 push complete\n");
  12.    if(arg)
  13.        return((void*)1);
  14.    pthread_cleanup_pop(0);
  15.    pthread_cleanup_pop(0);
  16.    return((void*)1);
  17. }
  18. void * thr_fn2(void * arg)
  19. {
  20.    printf("thread 2 start\n");
  21.    pthread_cleanup_push(cleanup, "thread 2 first handler");
  22.    pthread_cleanup_push(cleanup, "thread 2 second handler");
  23.    printf("thread 2 push complete\n");
  24.    if(arg)
  25.        pthread_exit((void*)2);
  26.    pthread_cleanup_pop(0);
  27.    pthread_cleanup_pop(0);
  28.    pthread_exit((void*)2);
  29. }
  30. int main(void)
  31. {
  32.    int err;
  33.    pthread_t tid1, tid2;
  34.    void *tret;
  35.    
  36.    err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);
  37.    if(err != 0)
  38.        err_quit("can‘t create thread 1 %s\n", strerror(err));
  39.    err = pthread_create(&tid2, NULL, thr_fn2, (void*)2);
  40.    if(err != 0)
  41.        err_quit("can‘t create thread 2 %s\n", strerror(err));
  42.    err = pthread_join(&tid1, &tret);
  43.    if(err != 0)
  44.        err_quit("can‘t join thread 1 %s\n", strerror(err));
  45.    printf("thread 1 exit code %d\n", (int)tret);
  46.    err = pthread_join(&tid2, &tret);
  47.    if(err != 0)
  48.        err_quit("can‘t join thread 2 %s\n", strerror(err));
  49.    printf("thread 2 exit code %d\n", (int)tret);
  50.    exit(0);
  51. }

运行后结果:

  1. $ ./a.out
  2. thread 1 start
  3. thread 1 push complete
  4. thread 2 start
  5. thread 2 push complete
  6. cleanup:thread 2 second handler
  7. cleanup:thread 2 first handler
  8. thread 1 exit code 1
  9. thread 2 eixt code 2

从输出结果可以看出,两个线程都正常启动和退出,但只调用了第二个线程的清理处理程序,如果线程通过从它的启动例程中返回而终止的话,那么它的清理处理程序就不会被调用,还要注意处理程序是按照与它们安装时相反的顺序被调用的。

线程分离:

如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即回收。当线程被分离时,并不能用pthread_join函数等待它的终止状态,对分离状态的线程调用该函数会产生失败,返回EINVAL。调用pthread_detach可以用于使线程进入分离状态。

  1. #include <pthread.h>
  2. int pthread_detach(pthread_t tid);

线程同步


互斥锁:

可以通过使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。

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

  1. #include <pthread.h>
  2. int pthread_mutex_init(pthread_mutex_t *restruct mutex, const pthread_mutexattr_t *restrict attr);
  3. int pthread_mutex_destroy(pthread_mutex_t *mutex);   #返回值:若成功则返回0,否则返回错误编码

要用默认的属性初始化互斥量,只需把attr设置为NULL。

加锁解锁:

  1. #include <pthread.h>
  2. int pthread_mutex_lock(pthread_mutex_t *mutex);    #加锁
  3. int pthread_mutex_trylock(pthread_mutex_t *mutex); #尝试加锁
  4. int pthread_mutex_unlock(pthread_mutex_t *mutex);  #解锁
  5.                                                   #返回值:若成功返回0,否则返回错误编号

注:如果互斥量已经上锁,调用线程将阻塞到互斥量被解锁。

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock时互斥量处于未锁住状态,则pthread_mutex_trylock锁住互斥量,否则pthread_mutex_trylock就好失败,不能锁住互斥量,返回EBUSY。

死锁:

1,如果线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态。

2,只有在一个线程试图以与另一个线程相反的顺序锁住互斥量时,才可能出现死锁。

遇到死锁解决:可以先释放占有的锁,然后过一段时间再试,这种情况可以使用pthread_mutex_trylock接口避免死锁。

锁的性能分析:

如果锁的粒度太粗,即使用少的互斥量却要长的锁住时间,就会出现很多线程阻塞等待相同的锁并且增加了代码的复杂度;而锁的粒度太细,即使用过多的互斥量,这样过多的锁的开销会使系统性能下降。总之,锁的粒度要根据具体情况具体分析,在代码复杂性和优化性能之间找到平衡点。

读写锁:

  1. #include <pthread.h>
  2. int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); #初始化
  3. int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);    #销毁读写锁
  4. int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock);     #读模式下锁定读写锁,即别的线程可读不可写
  5. int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock);     #写模式下锁定读写锁,即别的线程不可读不可写
  6. int pthread_rwlock_tryrdlock(pthread_rwlock_t *restrict rwlock);  #读模式下尝试锁定读写锁
  7. int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock);  #写模式下尝试锁定读写锁
  8. int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock);     #解锁
  9.                                                                  #返回值:若成功返回0,否则返回错误编号

注:如普通锁一样,也要对读写锁进行初始化,如果希望读写锁有默认的属性,可以传一个空指针给attr即可。

注:只有线程搜索队列的频率远远高于增加或删除作业时,使用读写锁才可能改善性能。

条件变量:

条件变量与互斥量一起使用时,运行线程以无竞争的方式等待特定的条件发生。

条件变量用pthread_cond_t数据类型表示,与互斥量一样也需要初始化,可以把常量PTHREAD_COND_INITALIZER赋给静态分配的条件变量,若条件变量是动态分配的,则需要用pthread_cond_init函数初始化,并在内存释放前调用pthread_cond_destroy。

  1. #include <pthread.h>
  2. int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  3. int pthread_cond_destroy(pthread_cond_t *cond);   #返回值:若成功则返回0,否则返回错误编码
要用默认的属性初始化互斥量,只需把attr设置为NULL。

使用pthread_cond_wait等待条件变为真,如果在给定的时间内条件不能满足,那么会生成一个代表出错码的返回变量

  1. #include <pthread.h>
  2. int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  3. int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);  
  4.                                                                                                                              #返回值:若成功则返回0,否则返回错误编码

传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。函数返回时,互斥量再次被锁住。

timeout是个结构,通过timespec结构指定

  1. struct timespec{
  2.    time_t tv_sec; #秒
  3.    long tv_nsec; #纳秒
  4. }

注:使用这个结构时,时间值是一个绝对值而不是相对值,例如,如果能等待1分钟,则需要把当前时间加上1分钟再转换到timespec结构。

  1. #include <pthread.h>
  2. int pthread_cond_signal(pthread_cond_t *restrict cond);
  3. int pthread_cond_broadcast(pthread_cond_t *restrict cond);  
  4.                                                          #返回值:若成功则返回0,否则返回错误编码

pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有线程。

示例:

  1. #include <pthread.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
  5. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
  6. void *thread1(void *);
  7. void *thread2(void *);
  8. int i=1;
  9. int main(void)
  10. {
  11.    pthread_t t_a;
  12.    pthread_t t_b;
  13.    pthread_create(&t_a,NULL,thread1,(void *)NULL);/*创建进程t_a*/
  14.    pthread_create(&t_b,NULL,thread2,(void *)NULL); /*创建进程t_b*/
  15.    pthread_join(t_a, NULL);/*等待进程t_a结束*/
  16.    pthread_join(t_b, NULL);/*等待进程t_b结束*/
  17.    pthread_mutex_destroy(&mutex);
  18.    pthread_cond_destroy(&cond);
  19.    exit(0);
  20. }
  21. void *thread1(void *junk)
  22. {
  23.    for(i=1;i<=6;i++)
  24.    {
  25.        pthread_mutex_lock(&mutex);/*锁住互斥量*/
  26.      printf("thread1: lock %d/n", __LINE__);
  27.        if(i%3==0)
  28.        {
  29.         printf("thread1:signal 1  %d/n", __LINE__);
  30.            pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b进程*/
  31.         printf("thread1:signal 2  %d/n", __LINE__);
  32.         sleep(1);
  33.      }
  34.        pthread_mutex_unlock(&mutex);/*解锁互斥量*/
  35.      printf("thread1: unlock %d/n/n", __LINE__);
  36.      sleep(1);
  37.    }
  38. }
  39. void *thread2(void *junk)
  40. {
  41.    while(i<6)
  42.    {
  43.        pthread_mutex_lock(&mutex);
  44.      printf("thread2: lock %d/n", __LINE__);
  45.      if(i%3!=0)
  46.        {
  47.         printf("thread2: wait 1  %d/n", __LINE__);
  48.            pthread_cond_wait(&cond,&mutex);/*解锁mutex,并等待cond改变*/
  49.         printf("thread2: wait 2  %d/n", __LINE__);
  50.      }
  51.        pthread_mutex_unlock(&mutex);
  52.      printf("thread2: unlock %d/n/n", __LINE__);
  53.      sleep(1);
  54.    }
  55. }

结果前部分:

  1. [X$ ./a.out
  2. thread1: lock 30
  3. thread1: unlock 40
  4. thread2: lock 52
  5. thread2: wait 1  55
  6. thread1: lock 30
  7. thread1: unlock 40
  8. thread1: lock 30
  9. thread1:signal 1  33
  10. thread1:signal 2  35
  11. thread1: unlock 40

信号量执行过程:

1),这里假设thread1先进入循环,然后获得锁,此时估计thread2执行,阻塞在 pthread_mutex_lock(&mutex),直到thread1释放mutex锁 pthread_mutex_unlock(&mutex);/*解锁互斥量*/;

2),然后thread2得已执行,获取metux锁,满足if条件,到pthread_cond_wait (&cond,&mutex);/*等待*/这里的thread2阻塞,不仅仅是等待cond变量发生改变,同时释放mutex锁(执行pthread_cond_wait时会先释放之前锁住的互斥量,返回后再次加锁)。

3),mutex锁释放后,thread1终于获得了mutex锁,得已继续运行,当线程1的if(i%3==0)的条件满足后,通过pthread_cond_signal发送信号,告诉等待cond的变量的线程(这个情景中是线程二),cond条件变量已经发生了改变。

总之,pthread_cond_wait之前会锁住互斥量,然后释放,等到pthread_cond_signal使条件变量改变时才停止阻塞,继续往下执行,即执行pthread_cond_wait的线程,需要得到执行pthread_cond_signal的线程的条件才会停止阻塞。

0 0
原创粉丝点击