Unix高级IO之多线程编程 ——pthread解析
来源:互联网 发布:mac pro如何重装系统 编辑:程序博客网 时间:2024/05/17 03:23
线程
线程包含了表示进程内执行环境必须的信息,其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。
进程的所以信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
标识:每个线程都有一个线程ID,且该ID只在它所属的进程环境中有效。
获得自身的线程ID:
#include <pthread.h>
pthread_t pthread_self(void); #返回值为:调用线程的线程ID
创建线程:
#include <pthread.h>
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
#include <pthread.h>
void pthread_exit(void *rval_ptr);
rval_ptr是一个无类型指针,进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr); #返回值:若成功则返回0,失败返回错误编号
调用pthread_join函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者取消。
当线程取消时,pthread_join返回,会根据情况把取消的线程的返回值置于rval_ptr中,若对线程的返回值不感兴趣,可以把rval_ptr置为NULL。
取消同一进程中的其他线程:
#include <pthread.h>
void pthread_cancel(pthread_t tid);
注意,该方法并不等待线程终止,它仅仅是提出请求。
线程清理处理程序
线程可以安排它退出时需要调用的函数,这就是线程清理处理程序要做的工作。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute); #无论哪种情况,该函数都会删除上次pthread_cleanup_push调用建立的清除处理程序
示例:
#include "apue.h"
#include <pthread.h>
void cleanup(void *arg)
{
printf("cleanup: %s\n", (char*)arg);
void * thr_fn1(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);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return((void*)1);
}
void * thr_fn2(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);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);
if(err != 0)
err_quit("can‘t create thread 1 %s\n", strerror(err));
err = pthread_create(&tid2, NULL, thr_fn2, (void*)2);
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 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 thread 2 %s\n", strerror(err));
printf("thread 2 exit code %d\n", (int)tret);
exit(0);
}
运行后结果:
$ ./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 eixt code 2
从输出结果可以看出,两个线程都正常启动和退出,但只调用了第二个线程的清理处理程序,如果线程通过从它的启动例程中返回而终止的话,那么它的清理处理程序就不会被调用,还要注意处理程序是按照与它们安装时相反的顺序被调用的。
线程分离:
如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即回收。当线程被分离时,并不能用pthread_join函数等待它的终止状态,对分离状态的线程调用该函数会产生失败,返回EINVAL。调用pthread_detach可以用于使线程进入分离状态。
#include <pthread.h>
int pthread_detach(pthread_t tid);
线程同步
互斥锁:
可以通过使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。
互斥变量用pthread_mutex_t数据类型表示,在使用互斥变量之前,必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restruct mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#返回值:若成功则返回0,否则返回错误编码
要用默认的属性初始化互斥量,只需把attr设置为NULL。
加锁解锁:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex); #加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex); #尝试加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); #解锁
#返回值:若成功返回0,否则返回错误编号
注:如果互斥量已经上锁,调用线程将阻塞到互斥量被解锁。
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock时互斥量处于未锁住状态,则pthread_mutex_trylock锁住互斥量,否则pthread_mutex_trylock就好失败,不能锁住互斥量,返回EBUSY。
死锁:
1,如果线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态。
2,只有在一个线程试图以与另一个线程相反的顺序锁住互斥量时,才可能出现死锁。
遇到死锁解决:可以先释放占有的锁,然后过一段时间再试,这种情况可以使用pthread_mutex_trylock接口避免死锁。
锁的性能分析:
如果锁的粒度太粗,即使用少的互斥量却要长的锁住时间,就会出现很多线程阻塞等待相同的锁并且增加了代码的复杂度;而锁的粒度太细,即使用过多的互斥量,这样过多的锁的开销会使系统性能下降。总之,锁的粒度要根据具体情况具体分析,在代码复杂性和优化性能之间找到平衡点。
读写锁:
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); #初始化
int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock); #销毁读写锁
int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock); #读模式下锁定读写锁,即别的线程可读不可写
int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock); #写模式下锁定读写锁,即别的线程不可读不可写
int pthread_rwlock_tryrdlock(pthread_rwlock_t *restrict rwlock); #读模式下尝试锁定读写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock); #写模式下尝试锁定读写锁
int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock); #解锁
#返回值:若成功返回0,否则返回错误编号
注:如普通锁一样,也要对读写锁进行初始化,如果希望读写锁有默认的属性,可以传一个空指针给attr即可。
注:只有线程搜索队列的频率远远高于增加或删除作业时,使用读写锁才可能改善性能。
条件变量:
条件变量与互斥量一起使用时,运行线程以无竞争的方式等待特定的条件发生。
条件变量用pthread_cond_t数据类型表示,与互斥量一样也需要初始化,可以把常量PTHREAD_COND_INITALIZER赋给静态分配的条件变量,若条件变量是动态分配的,则需要用pthread_cond_init函数初始化,并在内存释放前调用pthread_cond_destroy。
要用默认的属性初始化互斥量,只需把attr设置为NULL。
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond); #返回值:若成功则返回0,否则返回错误编码
使用pthread_cond_wait等待条件变为真,如果在给定的时间内条件不能满足,那么会生成一个代表出错码的返回变量
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
#返回值:若成功则返回0,否则返回错误编码
传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。函数返回时,互斥量再次被锁住。
timeout是个结构,通过timespec结构指定
struct timespec{
time_t tv_sec; #秒
long tv_nsec; #纳秒
}
注:使用这个结构时,时间值是一个绝对值而不是相对值,例如,如果能等待1分钟,则需要把当前时间加上1分钟再转换到timespec结构。
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *restrict cond);
int pthread_cond_broadcast(pthread_cond_t *restrict cond);
#返回值:若成功则返回0,否则返回错误编码
pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有线程。
示例:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;/*初始化条件变量*/
void *thread1(void *);
void *thread2(void *);
int i=1;
int main(void)
{
pthread_t t_a;
pthread_t t_b;
pthread_create(&t_a,NULL,thread1,(void *)NULL);/*创建进程t_a*/
pthread_create(&t_b,NULL,thread2,(void *)NULL); /*创建进程t_b*/
pthread_join(t_a, NULL);/*等待进程t_a结束*/
pthread_join(t_b, NULL);/*等待进程t_b结束*/
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
void *thread1(void *junk)
{
for(i=1;i<=6;i++)
{
pthread_mutex_lock(&mutex);/*锁住互斥量*/
printf("thread1: lock %d/n", __LINE__);
if(i%3==0)
{
printf("thread1:signal 1 %d/n", __LINE__);
pthread_cond_signal(&cond);/*条件改变,发送信号,通知t_b进程*/
printf("thread1:signal 2 %d/n", __LINE__);
sleep(1);
}
pthread_mutex_unlock(&mutex);/*解锁互斥量*/
printf("thread1: unlock %d/n/n", __LINE__);
sleep(1);
}
}
void *thread2(void *junk)
{
while(i<6)
{
pthread_mutex_lock(&mutex);
printf("thread2: lock %d/n", __LINE__);
if(i%3!=0)
{
printf("thread2: wait 1 %d/n", __LINE__);
pthread_cond_wait(&cond,&mutex);/*解锁mutex,并等待cond改变*/
printf("thread2: wait 2 %d/n", __LINE__);
}
pthread_mutex_unlock(&mutex);
printf("thread2: unlock %d/n/n", __LINE__);
sleep(1);
}
}
结果前部分:
[X$ ./a.out
thread1: lock 30
thread1: unlock 40
thread2: lock 52
thread2: wait 1 55
thread1: lock 30
thread1: unlock 40
thread1: lock 30
thread1:signal 1 33
thread1:signal 2 35
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的线程的条件才会停止阻塞。
- Unix高级IO之多线程编程 ——pthread解析
- Unix高级IO之多线程编程 ——用户级线程和内核级线程
- Unix高级IO之多线程编程 ——可重入函数与不可重入函数
- Unix线程基础编程pthread
- UNIX环境高级编程——线程
- UNIX环境高级编程——线程
- UNIX环境高级编程——线程
- unix环境高级编程-线程解析
- UNIX环境高级编程——线程限制 && 线程属性
- 《unix高级环境编程》线程——线程基本概述
- 《unix高级环境编程》线程——线程终止
- 《unix高级环境编程》线程——线程同步
- 《unix高级环境编程》线程控制——线程属性
- 《unix高级环境编程》线程——线程基本概述
- 《unix高级环境编程》线程——线程终止
- 《unix高级环境编程》线程——线程同步
- 《unix高级环境编程》线程控制——线程属性
- iOS之多线程——概念、pthread、NSThread、GCD
- iPhone6中获取到的UITableViewCell的size.width为啥还是320?
- linux安装confluence5.8.10破解版
- linux安装confluence5.8.10破解版
- mysq 5.5 3大安装方法
- UEditor配置-上传图片或文件时提示后端配置未正常加载
- Unix高级IO之多线程编程 ——pthread解析
- Android 反编译与混淆技术完全解析
- linux下用shell杀死tomcat进程
- 深入浅出MAC,技巧部分汇总
- 数据结构之队列(四)
- 五子棋AI算法第五篇-算杀
- VS2010中,无法嵌入互操作类型“……”,请改用适用的接口的解决方法
- 网络请求参数加签处理
- 《Linux操作系统分析》之分析计算机如何启动以及如何工作运行