APUE笔记 线程控制

 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   


int pthread_attr_setguardsize(pthread_attr_t *attr,  size_t *restrict guardsize);

int pthread_attr_setguardsize(pthread_attr_t *attr,  size_t *restrict guardsize);

             如果修改了线程的stackaddr,则默guardsize为0

在栈末尾后,用以避免栈溢出的扩展内存大小

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 封装为原子操作!































 



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值