Linux 线程控制
Linux 线程控制
线程属性
同步原语属性
多线程间如何保持数据私有性
基于进程的系统调用如何与线程交互
线程限制
可通过 sysconf 函数查询线程限制。
注意:有些os下使用sysconf无法访问,只是没有提供访问方法,不意味这些限制不存在
线程属性
使用 pthread_attr_t 结构控制线程属性,需要初始化和销毁。
控制初始时是否为detached状态
detachstate可设为两个合法值之一: PTHREAD_CREATE_DETACHED
初始化为分离态 PTHREAD_CREATE_JOINABLE
正常启动
控制进程栈
对于进程,虚拟地址空间大小固定。因为进程中只有一个栈,所以它大小通常没问题。但对于线程,同样大小虚拟地址空间被所有线程栈共享。如果线程栈累计大小超过可用空间,要减小默认栈大小。stackaddr 时栈最低内存地址,但不一定是栈开始位置,通常栈是从高地址向低地址生长,所以stackaddr是栈结尾位置。
栈大小
gardsize
同步属性
互斥属性
重要的三个属性:
1. 进程共享
进程通过内存映射共享一个变量
PTHREAD_PROCESS_PRIVATE
(默认其他进程不能使用这个进程创建的锁) PTHREAD_PROCESS_SHARED
2. 健壮
PTHREAD_MUTEX_STALLED
(默认) PTHREAD_MUTEX_ROBUST
持有锁的进程终止时没有释放锁,另一个进程中的线程申请锁。前者等待解锁的进程会被“拖住”;后者不会被“拖住”,而是直接返回 EOWNERDEAD 而不是0
首先,需要了解的是,如果不对该互斥量做状态恢复,那么如果其他进程试图直接对该锁住的互斥量进行解锁的情况下,又有另外的进程试图获取该互斥量,那么试图获取该互斥量的阻塞线程会得到错误码 ENOTRECOVERABLE,发生这种情况会导致该互斥量处于永久不可用状态,此时,我们必须先调用 pthread_mutex_destroy 来 destroy 掉该锁,然后重新用锁的属性和锁的初始化函数来重新初始化该锁。
为了避免这样的问题,线程可以调用 pthread_mutex_consistent(pthread_mutex_t *mutex) 函数来恢复该锁的一致性,然后调用 pthread_mutex_unlock 对该互斥量解锁,接下来其他进程就可以对该互斥量进行加锁(也就是获取该互斥量)了,这样该互斥量的行为也就恢复正常了。
3. 类型
读写锁属性
条件变量属性
包含进程共享属性和时钟属性
时钟属性控制计算 pthread_cond_timedwait 函数的超时参数时使用哪个时钟。在使用 pthread_cond_timedwait 前要用 pthread_condattr_t 对象对条件变量进行初始化。
屏蔽属性
只有进程共享属性
线程私有数据
- 维护基于每个线程的数据
- 提供了让基于进程的接口适应了多线程环境的机制。比如errno
一个进程中所有线程都可以访问这个进程中整个地址空间,除了寄存器无法阻止访问它的数据。线程私有数据也不例外。底层实现不能阻止,但是可提高数据独立性。
在分配前获得一个与数据关联的键,用于获取线程对私有数据的访问。
创建的键存储在keyp指向的内存单元中,这个键可被进程中所有线程使用,每个线程把这个键与不同线程特定数据地址关联。关联的函数为析构函数,线程退出时调用,唯一参数就是该数据地址。
取消选项
pthread_cancel 调用发出取消请求消息。线程在取消请求发出后继续运行,直到线程到某个取消点(即某些POSIX定义的常见函数)。线程启动的默认状态为 PTHREAD_CANCEL_ENABLE,当设为 PTHREAD_CANCEL_DISABLE 时,取消信号不会杀死线程并且取消请求处于挂起状态,当取消状态再变为 PTHREAD_CANCEL_ENABLE 时,线程再下一个取消点处理请求。
可以手动调用专门的取消点函数:
以上为推迟取消,在到达取消点前不会取消。修改为异步取消,线程可以在任意时间撤销。
type 可选:PTHREAD_CANCEL_DEFERRED
PTHREAD_CANCEL_ASYNCHRONOUS
线程和信号
每个线程都有自己的屏蔽字,但信号处理时进程中所有线程共享的。进程中的信号发到单个线程,如果信号与硬件故障相关那么发到引起该事件的线程中,其他信号发送到任意一个线程。
线程中设置屏蔽字
当等候的信号处于挂起状态时sigwait将无阻塞的返回,返回前移除处于挂起状态等待状态的信号。如果支持排队,那么只移除一个。
为避免错误,在调用sigwait前,必须阻塞那些它在等待的信号,sigwait会取消信号集的阻塞状态,直到信号送到。返回前恢复线程屏蔽字。
多个线程在sigwait调用中等待一个信号而阻塞,那么信号到时只有一个线程能返回。
如果一个信号被sigaction注册了处理程序,一个线程调用中等待同一信号,这时由os决定如何,这两种情况不会同时触发。
线程和 fork
调用fork后,子进程只有一个线程,是父进程中调用fork的线程的副本。它继承了地址空间的副本,还从父进程继承了每个互斥量、读写锁、条件变量。但子进程不包含占有锁的进程的副本,所以子进程没法知道占用了哪些锁、要释放哪些锁。如果fork后马上调用exec可以避免这个问题。
线程与 IO
多线程环境下使用pread 和 pwrite,因为进程中所有线程共享相同的文件描述符。