12.1 引言
*线程控制包括在同一进程中的多个线程之间如何保持数据的私有性,以及基于进程的系统调用如何与线程进行交互
12.2 线程限制
*线程限制的使用时为了增强应用程序在不同的操作系统实现之间的可移植性
12.3 线程属性
*线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小
*线程的属性还包括可取消状态、可取消类型、并发度
*并发度控制着用户级线程可以映射的内核线程或进程的数目
*如果操作系统的实现在内核级的线程和用户级的线程之间保持一对一的映射,那么改变并发度并不会有什么效果,因为所有的用户级的线程都可能被调度到
*如果操作系统的实现让用户级线程到内核级线程或进程之间的映射关系式多对一的话,那么在给定时间内增加可运行的用户级线程数,肯呢过会改善性能
12.4 同步属性
*互斥量属性中两个重要的属性是进程共享属性和类型属性
*存在这样的机制,允许相互独立的多个进程把同一个内存区域映射到它们各自独立的地址空间中。就像多个线程访问共享数据一样,多个进程访问共享数据通常也需要同步。如果进程共享互斥量的属性设置为PTHREAD_PROCESS_SHARED,从多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步
*PTHREAD_MUTEX_RECURSIVE互斥量类型允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。用一个递归互斥量维护锁的计数,在解锁的次数和加锁的次数不相同的情况下不会释放锁
*PTHREAD_MUTEX_DEFAULT类型可以用于请求默认语义
*不占用时解锁指的是一个线程对被另一个线程加锁的互斥量进行解锁的情况
*互斥量用于保护与条件变量关联的条件
*如果需要把现有的单线程接口放到多线程环境中,递归互斥量是非常有用的
*为了保持接口与原来相同,可以把互斥量嵌入到数据结构中,把这个数据结构的地址作为参数引入。这种方案只有在为该数据结构提供了分配函数时才可行,所以应用并不知道数据结构的大小(假设在其中增加互斥量后必须扩大该数据结构的大小)
12.5 重入
*如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数时线程安全的
*很多函数并不是线程安全的,因为他们返回的数据是存放在静态的内存缓冲区中。通过修改接口,要求调用者自己提供缓冲区可以使函数变为线程安全的
*可以使用flockfile和ftrylockfile获取与给定FILE对象关联的锁。这个锁是递归的,当占有这把锁的时候,还可以再次获取该锁,这并不会导致死锁
*虽然标准的I/O例程从它们各自的内部数据结构这一角度出发,可能是以线程安全的方式实现的,但有时把锁开发给应用程序仍然是非常有用的。这允许应用程序把多个对标准I/O函数的调用组合成原子序列
12.6 线程私有数据
*线程私有数据(也称线程特定数据)是存储和查询与某个线程相关的数据的一种机制
*除了创建键以外,pthread_key_create可以选择为该键关联析构函数,当线程退出时,如果数据地址以及被置为非null数值,那么析构函数就会被调用,它唯一的参数就是该数据地址
*线程可以为线程私有数据分配多个键,每个键都可以有一个析构函数与它关联
12.7 取消选项
*有两个线程属性并没有包含在pthread_attr_t结构中,它们是可取消状态和可取消类型
*pthread_setcancelstate把当前的可取消状态设置为state,把原来的可取消状态存放在由oldstate指向的内存单元中,这两步是原子操作
*pthread_cancel调用并不等待线程终止,在默认情况下,线程在取消请求发出以后还是继续运行,直到线程到达某个取消点。取消点是线程检查是否被取消并按照请求进行动作的一个位置
*应用程序可以调用pthread_testcancel函数在程序中自己添加取消点。调用pthread_testcancel时,如果有某个取消请求正处于未决状态,而且取消并没有置为无效,那么线程就会被取消。但是如果取消被置为无效时,pthread_testcancel调用就没有任何效果
12.8 线程和信号
*每个线程都有自己的信号屏蔽字,但是信号的处理时进程中所以线程共享的。这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为以后,所以的线程都必须共享这个处理行为的改变。这样如果一个线程选择忽略某个信号,而其他的线程可以恢复信号的默认处理行为,或者为信号设置一个新的处理程序,从而可以撤销上述线程的信号选择
12.9 线程和fork
*用pthread_atfork函数最多可以安装三个帮助清理锁的函数
*prepare fork处理程序由父进程在fork创建子进程前调用,这个fork处理程序的任务是获取父进程定义的所有锁
*parent fork处理程序是在fork创建了子进程以后,但在fork返回之前在父进程环境中调用的,这个fork处理程序的任务是对prepare fork处理程序所获得的所以锁进行解锁
*child fork处理程序在fork返回之前在子进程环境中调用,与parent fork处理程序一样,child fork处理程序也必须释放prepare fork处理程序获得的所有锁
12.10 线程和I/O
*pread和pwrite函数将偏移量的设定和数据的读取/写入组合为一个原子操作,可以解决并发线程对同一文件进行读写操作的问题