本章介绍了一个进程中多个线程之间如何保持数据的似有性及进程的系统调用如何与线程进行交互。
1、线程限制:
Single Unix定义了一线线程操作的限制,和其他的限制一样,可以通过sysconf来查询。和其它的限制使用目的一样,为了应用程序的在不同操作 系统的可移植性。 一些限制:
PTHREAD_DESTRUCTOR_ITERATIONS: 销毁一个线程数据最大的尝试次数,可以通过_SC_THREAD_DESTRUCTOR_ITERATIONS作为sysconf的参数查询。
PTHREAD_KEYS_MAX: 一个进程可以创建的最大key的数量。可以通过_SC_THREAD_KEYS_MAX参数查询。
PTHREAD_STACK_MIN: 线程可以使用的最小的栈空间大小。可以通过_SC_THREAD_STACK_MIN参数查询。
PTHREAD_THREADS_MAX:一个进程可以创建的最大的线程数。可以通过_SC_THREAD_THREADS_MAX参数查询
2、线程属性
在调用pthread_create函数创建一个新线程时候可以指定线程的属性,属性类型为pthread_attr_t,该结构对应用程序是不透明,操作函数如下:
int pthread_attr_init(pthread_attr_t *attr); //初始化线程属性
int pthread_attr_destroy(pthread_attr_t *attr); //释放线程属性空间
线程属性主要有:(1)线程的分离状态属性detachstate,(2)线程栈末尾的警戒缓冲区大小guardsize,(3)线程栈的最低地址statckaddr,(4)线程栈的大小stacksize。
如果对现有某个线程的终止状态不感兴趣的话,可以使用pthread_detach函数让操作系统在线程退出时候收回它所占用的资源。创建线程时候可以修改pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。可以使用下面函数进程操作分离属性:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
detatchstate取值为:(1)PTHREAD_CREATE_DETACHED 分离状态启动,(2)PTHREAD_CREATE_JOINABLE 正常启动,应用程序可以获取线程的终止状态。
线程栈属性操作函数:
int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize);
int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr, size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
3、同步属性
(1)互斥量属性:有进程共享属性和类型属性两种,进程共享属性是可选的,互斥量属性数据类型为pthread_mutexattr_t。在进程中,多个线程可以访问同一个同步对象,默认情况进程共享互斥量属性为:PTHREAD_PROCESS_PRIVATE。互斥量属性操作函数如下:
int pthread_mutexattr_init(pthread_mutexattr_t *attr); //初始化
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); //回收
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared); //查询进程共享属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared); //设置进程共享属性
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type); //查询类型属性
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); //设置类型属性
类型互斥量控制着互斥量的特性,POSIX.1定义了四种类型:
互斥量类型 | 用途 | 没有解锁时再次加锁 | 不占用是解锁 | 在已解锁是解锁 |
PTHREAD_MUTEX_NORMAL | 标准类型,不做任何检查 | 死锁 | 未定义 | 未定义 |
PTHREAD_MUTEX_ERRORCHECK | 进程错误检查 | 返回错误 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_RECURSIVE | 避免死锁 | 允许 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_DEFFAULT | 请求默认语义 | 未定义 | 未定义 | 未定义 |
PTHREAD_MUTEX_RECURSIVE互斥量类型允许在同一个线程在互斥量解锁之前对该互斥量进程多次加锁,当对个一个量加锁两次的时候,可以避免死锁。例如用默认类型,当对一个锁加锁两次时候,会造成死锁
(2)读写锁属性:与互斥量类似,但是只支持进程共享唯一属性,操作函数原型如下:
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int pshared);
(3)条件变量属性:也是只支持进程共享属性,操作函数原型如下:
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);
4、重入
有了信号处理程序和多线程,多个控制线程在同一时间可能潜在的调用同一个函数,如果一个函数在同一时刻可以被多个线程安全调用,则称为函数是线程安全的。很多函数并不是线程安全的,因为它们返回的数据是存放在静态的内存缓冲区,可以通过修改接口,要求调用者自己提供缓冲区使函数变为线程安全的。POSIX.1提供了以安全的方式管理FILE对象的方法,使用flockfile和ftrylockfile获取与给定FILE对象关联的锁。这个锁是递归锁。函数原型如下:
void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);
为了避免标准I/O在一次一个字符操作时候频繁的获取锁开销,出现了不加锁版本的基于字符的标准I/O例程。函数如下:
int getc_unlocked(FILE *stream);
int getchar_unlocked(void);
int putc_unlocked(int c, FILE *stream);
int putchar_unlocked(int c);
5、线程似有数据
线程似有数据时存储和查询与某个线程相关的数据的一种机制,希望每个线程可以独立的访问数据副本,而不需要担心与其他线程的同步访问问题。进程中的所有线程都可以访问进程的整个地址空间,除了使用寄存器以外,线程没有办法阻止其他线程访问它的数据,线程似有数据也不例外。管理线程私有数据的函数可以提高线程间的数据独立性。
分配线程私有数据过程:首先调用pthread_key_create创建与该数据关联的键,用于获取对线程私有数据的访问权,这个键可以被进程中所有线程访问,但是每个线程把这个键与不同的线程私有数据地址进行关联然后通过调用pthread_setspecific函数吧键和线程私有数据关联起来,可以通过pthread_getspecific函数获取线程私有数据的地址。
pthread_key_create函数可以选择为该键关联的析构函数,调用pthread_key_delete函数来取消与线程私有数据值之间的关联关系。通过调用pthread_once函数确保分配的键并不会由于在初始化阶段的竞争而发生变动。
操作函数如下:
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
int pthread_once(pthread_once_t *once_control,void (*init_routine)(void)); //避免竞争条件
pthread_once_t once_control = PTHREAD_ONCE_INIT;
void *pthread_getspecific(pthread_key_t key); //返回线程私有数据值,没有返回NULL
int pthread_setspecific(pthread_key_t key, const void *value); //设置线程私有数据
6、取消选项
取消选项包括可取消状态和可取消类型,针对线程在响应pthread_cancel函数调用时候所呈现的行为。可取消状态取值为:PTHREAD_CANCLE_ENABLE (默认的可取消状态)或PTHREAD_CANCLE_DISABLE。取消类型也称为延迟取消,类型可以为:PTHREAD_CANCLE_DEFERRED或PTHREAD_CANCEL_ASYNCHRONOUS。通过下面函数进行设置取消状态和取消类型:
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
void pthread_testcancel(void); //自己添加取消点
7、线程和信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。进程中的信号是传递到单个线程的,进程中的信号屏蔽函数sigprocmask函数在线程中没有定义,线程中必须使用pthread_sigmask。线程可以调用sigwait函数等待一个或者多个信发送。调用pthread_kill函数将信号发送到线程。具体函数原型如下:
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
int sigwait(const sigset_t *set, int *sig);
int pthread_kill(pthread_t thread, int sig);
8、线程和fork
父进程调用fork为子进程创建了整个进程地址空间的副本,子进程从父进程那里继承了所有互斥量、读写锁和条件变量的状态。如果父进程包括多个线程,子进程在fork返回以后,如果紧接着不马上调用exec的话,就需要清理锁。在子进程内部只存在一个线程,它是由父进程中调用fork的线程的副本构成的,父进程中的线程占有锁,则子进程同样占有锁,但是子进程不包含占有锁的线程的副本。通过pthread_atfork函数建立fork处理程序清除锁状态。函数原型如下:
int pthread_atfork(void (*prepare)(void), void (*parent)(void),void (*child)(void));
prepare处理程序由父进程在fork创建子进程前调用,获取父进程定义的所有锁。parent处理程序在fork创建子进程以后,但在fork返回之前在父进程环境调用,对prepare处理程序获得的所有锁进行解锁,child处理程序在fork返回之前在子进程环境中调用,也必须释放prepare处理程序获得的所有锁。parent和child处理程序与它们注册时顺序相同,prepare处理程序调用则与注册时的顺序相反。