3. 线程控制
1). 线程属性
目标:可以设置 线程的 detached/join 状态,线程栈的大小和最低地址等属性。
detached/join 状态的区别:
当线程处于 分离状态(detached)时,线程结束时,os立即回收资源。主线程不可以调用pthread_join获取线程退出时的返回值。
当线程处于 未分离状态(join)时,线程结束时,主线程 调用pthread_join获取线程退出时的返回值, 随后释放该线程资源。
a)数据类型 pthread_attr_t
b)初始化及释放属性结构 pthread_attr_init pthread_attr_destroy
初始化时 会分配内存,所以一定要与 _..destroy 函数相对应
c)获取或设置线程分离状态 pthread_attr_setdetachstate pthread_attr_getdetachstate
有2种可选的状态值:
PTHREAD_CREATE_DETACHED 分离状态
PTHREAD_CREATE_JOINABLE 正常状态,可以使用pthread_join来获取状态
d)应该获取pthread_atr_destroy的返回值,因为使用pthread_attr_init初始化的时候可能分配有内存,如果释放内存失败的话,
会造成内存泄漏
e)控制线程栈的空间的大小
需求:多个线程的栈空间累计超过了进程的可用虚拟地址空间
线程调用函数的自动变量很多,或者递归很深
1)管理stackaddr线程属性,管理stacksize线程属性
pthread_attr_getstack
pthread_attr_setstack
2)获取或设置线程栈的大小
pthread_attr_setstacksize 系统帮助分配内存,自己不用管
pthread_attr_getstacksize
3)线程栈的保护
默认大小为宏PAGESIZE,但修改了栈属性后,这个值就会变成0
pthread_attr_getguardsize
pthread_attr_setguardsize
f)线程属性-并发度
pthread_attr_setconcurency
pthread_attr_getconcurency
2). 同步属性 (线程的同步对象 (例:互斥量、读写锁、条件变量)的属性)
实现同步的3种方式中的对象的属性
a)互斥量属性 pthread_mutexattr_t
1)初始化及释放 pthread_mutexattr_init pthread_mutexattr_destroy
2)进程共享属性
获取与设置共享属性 pthread_mutexattr_getpshared pthread_mutexattr_setpshared
PTHREAD_PROCESS_PRIVAE 进程内的多个线程可以访问同一个同步对象,默认属性
PTHREAD_PROCESS_SHARED 多个进程可以共享同一块内存区域 内存共享技术。将互斥量用于进程间对同一内存区访问的同步
3)互斥量类型属性
目标:设置 同一线程对已上锁的互斥量再次上锁 是否 死锁等属性。
PTHREAD_MUTEX_NORMAL 标准的互斥量类型,不做错误检查或死锁检查
PTHREAD_MUTEX_DEFAULT 依赖于操作系统提供到其他类型的映射
PTHREAD_MUTEX_ERRORCHECK 提供错误检查
PTHREAD_MUTEX_RECURSIVE (递归锁)允许多次加锁,但是需要解锁对应次数 tmd,这个类型叫做递归锁
获取与设置互斥量类型属性
pthread_mutexattr_gettype pthread_mutexattr_settype
应用场景:
PTHREAD_MUTEX_RECURSIVE (递归锁)
当将现有的单线程接口放到多线程环境中时,使用递归锁
例:
同一线程中 func1、 func2分别对 互斥量加锁, 若不是用 递归锁则会出现死锁。
b)读写锁属性 pthread_rwlockattr_t
可以设置是否支持进程共享属性 (默认为 只支持 线程共享属性 )
c)条件变量属性pthread_condattr_t
可以设置是否支持进程共享属性 (默认为 只支持 线程共享属性
)
4. 线程重入
可重入函数--指在信号处理函数中,正在执行的程序被信号处理程序中断后,返回时 不能正确运行的函数。
原因:(a)使用了静态数据结构--全局的,会被其他线程/信号处理函数 等 使用
(b)调用了 malloc / free ( malloc 为它所分配的存储区维护一个链接表,执行信号处理函数时,进程可能正在修改
该表,导致被破环)
(c)标准I/O函数--大部分使用了 全局结构变量
1)线程安全:如果一个函数同一时刻可以被多个线程安全地调用
2)系统是否支持线程安全函数 sysconf(_POSIX_THREAD_SAFE_FUNCTIONS)
非线程安全的原因:返回的数据存放在静态的内存缓存区,多个线程调用该函数的时候 会覆盖前面正在使用的区域。
将其进行可重入,为其分配自己的缓存器,避免被其他线程干扰,可以保证是线程安全的。
重入后的函数,成为线程安全,并不意味着 它对信号处理程序是可重入的。(请举例,不太懂??)
3)异步-信号安全:如果函数对异步信号处理程序的重入是安全的
4)锁文件的3个函数
flockfile ftrylockfile funlockfile
该锁是递归锁
5)确保函数在进程里面只被调用一次
pthread_once_t var = PTHREAD_ONCE_INIT;
pthread_once(&var, function);
5. 线程私有数据
一种 让存储和查询 数据 与 某个线程相关的 机制。避免 与其他线程 同步访问的问题
1)需要的数据类型: pthread_key_t
2)创建私有数据的步骤
pthread_key_t key;
a)pthread_key_create(&key, 清理函数地址) 一般通过pthread_once确保函数只被执行一次,变量只被初始化一次
b)char* addr = pthread_getspecific(&key)
c)为addr分配内存 malloc
d)pthread_setspecific(&key, addr);
e)pthread_key_delete删除key
f)线程退出,执行清理函数地址
6. 线程取消
设置pthread_cancle 函数相关的属性项
1)线程可以被设置为是否可取消
pthread_setcancelstate(int state, int* oldstate)
2)pthread_cancel只是一个申请,只有线程到达了取消点才会取消.
3)延迟取消pthread_testcancel, 适合于没有取消点的函数
4)设置取消的类型pthread_setcanceltype
7. 线程与IO
pread,pwrite 原子io操作
例:
线程A
lseek( fd, 300, SEEK_SET);
read( fd, buf1, 100);
read( fd, buf1, 100);
线程B
lseek( fd, 700, SEEK_SET);
read( fd, buf2, 100);
read( fd, buf2, 100);
当 A执行完lseek,B在A调用read之前调用lseek, 最后 两线程 读取同一条记录 (对同一文件操作,后一个 偏移量设置 覆盖了前一个设置)。
解决方法:
pread--将偏移量设定和数据读取成为一个原子操作
线程A
pread( fd, buf1, 100, 300);
线程B
pread( fd, buf2, 100, 700);
8. 线程与信号
每个线程有自己的信号屏蔽字,但是他们共享
1)相同的信号处理函数 2)该信号与某函数的绑定,一个信号绑定到某个函数,这个被所有线程共享, 他们只能看到一个
多个线程公用进程的信号屏蔽机制,除了2种情况以外:
硬件故障的信号与计时器超时的信号,只递送给某个线程,其它的信号会发送给所有线程
pthread_sigmask
sigwait 等待信号发送. 一般操作需要先阻塞信号,sigwait调用会取消信号的阻塞状态,直到新信号到来
pthread_kill
sigwait(sigset_t*, int* signo)
sigwait的参数2表示捕获到的信号值
9. 线程与fork
pthread_atfork,理论内容相当多,过滤掉
当线程调用fork时,就会为子进程创建整个进程空间的副本。包括从父进程继承的互斥量、读写锁和条件变量等。
因此,如果父进程包含多个线程,子进程在fork返回后,没有调用exec (原来的地址空间会被丢弃)的话,就需要清理锁状态。
pthread_atfork( void (*prepare)(void), void (*parent)(void),void
(*child)(void));
可以设置3个锁清理函数。
prepare fork 由父进程 在fork创建子进程前调用,任务:获取父进程定义的所有锁。
parent fork: fork创建子进程后,但在fork返回之前,在父进程中 调用。
child fork :fork创建子进程后,但在fork返回之前,在子进程中 调用。
10. 同一进程的所有线程共享同一个计时器
11. 同一进程的所有线程共享相同的文件描述符