pthread_attr_destory 失败,无补救措施,内存泄漏,因为线程属性对应用程序是不透明的,用户不能操作属性的内存数据
线程属性
线程允许对线程相关的不同属性,设置属性值以细调线程行为
属性管理都以相同的形式,分别对应相应函数
属性初始化 pthread_XXattr_init
属性销毁 pthread_XXattr_destory
设置属性 pthread_XXattr_setXX
获取属性值 pthread_XXattr_getXX
线程的属性主要有栈 和 分离!
线程属性参数:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destory(pthread_attr_t *attr);
detachstate
int pthread_attr_setdetachstate(pthread_attr_t *attr, int *detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
对于分离的线程,即没有线程对此线程的终止状态感兴趣,设置为分离后,在线程结束式会立即回收线程的资源
PTHREAD_CREATE_DETACHED
PTHREAD_CREATE_JOINABLE
guardsize
如果修改了线程的stackaddr,则默guardsize为0int pthread_attr_setguardsize(pthread_attr_t *attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t *restrict guardsize);
在栈末尾后,用以避免栈溢出的扩展内存大小
stackaddr
int pthread_attr_setstack(pthread_attr_t *attr, size_t *restrict stksize);
int pthread_attr_getstack(pthread_attr_t *attr, size_t *restrict stksize);
进程虚地址空间大小是固定的,线程共享进程的栈地址空间,为确保所有线程的栈大小不超过进程的可用虚地址空间需要设置,线程的栈的大小以及起始地址
线程栈的最低地址,向高地址增长的此为栈起始地址,否则是栈的结尾
可用malloc的值地址起始,和大小设置线程的栈地址空间
也可以直接设置stacksize大小有系统根据设定大小设置线程栈的大小!
stacksize
int pthread_attr_setstack(pthread_attr_t *attr, size_t *restrict stksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t *restrict stksize);
成功返回0,错误返回错误码
互斥量属性
共享性
PTHREAD_PROCESS_PRIVETE
只有当前进程可访问
privte时,线程库一般能提供更高效的线程实现
PTHREAD_PROCESS_SHARED
进程间共享的,不同进程之间的线程可以以想同的互斥量来同步
int pthrast_mutexattr_setshares(pthread_mutexattr_t *attr, int pshared);
int pthrast_mutexattr_getshares(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
健壮性
类型
int pthrast_mutexattr_settype(pthread_mutexattr_t *attr, int pshared);
int pthrast_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
PTHREAD_MUTEX_NORMAL
一般标准互斥量类型,无错误检测
重复加/解锁,导致死锁
未占有锁,解锁未定义
PTHREAD_MUTEX_ERRORCHECK
提供错误检测
重复加/解锁,返回错误码
未占有锁,返回错误码
PTHREAD_MUTEX_RECURSIVE
同一线程可重复加锁,相同次数解锁才能释放锁,注意必须同一线程
未占有锁,解锁返回错误码
PTHREAD_MUTEX_DEFAULT
一般是设置上述三种中一种作为默认属性
读写锁属性
读写锁属性只有共享性一个,与互斥量相同
条件变量属性
共享性,与上的类似
时钟,指定在使用pthread_cond_timedwit时,等待哪个时钟!
屏障
只有共享性
重入
两个概念分清,线程安全,异步信号安全
线程安全指的是,在多个线程的情况下,线程并发调用函数,不会产生因为竞争访问数据、资源产生错误
而异步信号安全是指在异步信号处理是是安全的,不会产生错误
线程安全不一定是异步信号安全
系统中对非线程安全的函数有一线,线程安全实现以相同名字+_r命名
FILE对象的标准I/O是安全的,看起来类似于都调用了:
flockfile(FILE *fp);
funlockfile(FILE *fp);
ftrylockfile(FILE *fp);
若每个字节读写都要加锁,会比较慢,系统有实现非安全的字符I/O函数
int getchar_unlocked();int getchar_unlocked(FILE *fp);
int puttchar_unlocked(int c);int putchar_unlocked(int c, FILE *fp);
线程特定数据
线程可以访问统一进程的整个地址空间!,在底层无法防止其他线程访问本线程的内存数据!
线程特定数据的使用场景是,多个线程都需处理同一键值的数值,但值会依线程而不同!如errno,每个线程可能会产生不同的错误信息!
int pthread_key_create(pthread_key_t *key, void (*destruct_fun)(void *));
此处key 一般为全局变量,各个线程都可以访问
创建一个键,绑定析构函数
当线程以pthread_exit,正常退出,cancle的清理函数执行完毕是, 调用析构函数
以exit退出进程不会析构
以操作系统定义的顺序析构,看系统实现而定!
int pthread_key_delete(pthread_key_t key);
删除当前进程数据与键值的绑定
注意,不会释放内存,若直接调用不释放内存,会内存泄露!
key的创建可能会由于竞争,被创建多次,可以使用pthread_once,将创建放在fun函数中
int pthread_once(pthread_once_t *flag , void (*fun)(void));
flag = PTHREAD_ONCE_INIT;//只能等译这个宏
int pthread_setspecific(pthread_key_t key, const voud 8value);//成功0,否则返回错误码
void *pthread_getspecific(pthread_key_t key, const voud 8value);//当前线程无相关数据返回NULL
线程取消
线程可设置为PRHREAD_CANCEL_ENABLE,PTHREAD_CANCEL_DISABLE
int pthread_setcancelstat(int state, int *oldstat);
原子操作
disable下会将取消请求挂起,线程继续执行,直到变为enable
pthread_cancel调用后被取消线程并不立即取消,而是在到达下一个取消点时,执行取消的请求!
取消点,即检查是否线程被取消的程序位置,通常有一系列的函数,在执行之前都会执行取消检查,若有取消请求,则执行取消请求!
可以动态的调用取消检查
void pthread_testcalcel(void);
如果有取消请求被挂起,且当前为PRHREAD_CANCEL_ENABLE则执行取消,disable函数无效果
可以设置请求类型使得取消请求立即执行/延迟执行
int pthread_caneltype(int type, int *oldtype);
PTHREADCANCEL_DEFERED, PTHREADCANCEL_ASYNCHRONOUS
信号和线程
在无线程用sigwait函数时:
进程环境中,所有线程对信号的处理是共享的即,信号处理函数共享,在信号到来时,可能发给任意线程,在发给的线程中调用信号处理函数!
一个线程修改了信号的处理函数则所有的线程处理都改变
每个线程有自己的信号屏蔽字,可以选择忽略某些信号,(要是所有的线程都忽略了?)
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *oldset);
类似进程的mask,不过只对设置的线程有效
未设置信号处理函数:
可以设置sigwait函数,让某一个线程等待某些/个信号
让当前线程阻塞,直到某一信号到来!
如果没有设置信号处理函数,信号会发送给任一等待的线程(具体哪一个不确定)
#include <signal.h>
int sigwait(const sigset_t *restrict set, int *signop);
在执行时设置set为信号集,在返回后恢复原来的信号集
如果信号未阻塞,则在wait返回之前信号发给线程!
可以不设置信号处理函数,所有线程屏蔽指定信号,指定一个线程wait信号
则信号到来时,用指定线程处理信号!
因为wait后为执行处理代码,因为wait返回后,线成回复对信号的阻塞,所以信号处理不会被打断!(如果循环下次wait之前有信号,下次wait能不能取到信号?)
同时设置了信号处理函数和wait:
具体给谁由操作系统具体实现决定!
#include <pthread.h>
int pthread_kill(pthread_t tid, int signo);//成功返回0, 否则错误编码
向指定线程发送信号
signo==0, 检测是否线程存在
如果信号默认为杀死进程,则仍然传给线程,然后杀死整个进程!
线程&fork
当线程调用fork时,创建一个子进程
子进程中只有一个线程,是调用线程的副本
子进程拥有,父进程的互斥量、条件变量、读写锁的副本,状态也相同
so 父进程中被其他线程lock的所、信号量子进程中也被锁住,但 子进程只有一个线程,不知道哪些被占用需要释放
若直接exec则无影响,直接创建新空间!
否则可使用pthread_atfork
int pthread_atfork(void (*prepared)(void),void (*parent)(void), void (*child)(void));
prepare中 锁住所有要加锁的 变量,在fork之前调用
parent 解锁所有要解的锁 ,在fork后返回前,父进程的上下文调用
child 解锁所有要解的锁, 在fork后返回前,子进程的上下文调用
类似于 父进程lock 父进程unlock
子进程lock 子进程unlock
因为子进程复制父进程副本,并且写时复制!
可以用多套atfork
调用顺序,父/子 进程返回前,注册函数的调用顺序,与prepare相反
prepare lock atfork(A); atfork(B);atfork(C);
parent unlock C B A
child unlock C B A
局限
不能对条件变量、barrier 等的状态进行重新初始化!
递归互斥量不能清理,在child中
AND。。。不明白!
线程I/o
pread(fd,buf, 100, offset);
将seek 和 read 封装为原子操作!