作者:isshe
日期:2016.10.30
邮箱:i.sshe@outlook.com
github: https://github.com/isshe
1.前言
2. 相关概念
- 线程分离:
- 就是说与创建线程分离,当新建线程运行结束,就终止线程并释放资源。
- 默认情况下是不线程分离的,此时新线程运行结束后如果创建线程没有结束就等待创建线程结束,或者使用pthread_join()才能终止新线程,并回收资源。
- 线程分离方法:(通常在不关心线程终止状态时候使用)
- 使用pthread_detach函数,线程退出时回收资源。
- 从一开始就设置线程属性为线程分离:修改pthread_attr_t结构中的detachstate线程属性。
2.1 线程属性
- 在编译阶段使用_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE来检查系统是否支持线程栈属性。
- 在运行阶段使用_SC_THREAD_ATTR_STACKADDR 和 _SC_THREAD_ATTR_STACKSIZE参数调用sysconf检查系统是否支持线程栈属性。
- 当修改线程属性stackaddr时,会使警戒区无效,即guardsize==0。
- 互斥量用于保护条件变量关联的条件。在阻塞线程之前,pthread_cond_wait和pthread_cond_timewait函数释放与条件相关的互斥量。(注意!!!这个可以解答上一个博文的疑问)
2.2 重入
10.6节讨论了可重入函数和信号处理函数,线程在这两个方面也是类似的。
- 如果一个函数对多个线程来说是可重入的,则这个函数是线程安全的。(但不能说明对信号处理程序来说是可重入的)
- 线程安全:如果一个函数在同一个时间点被多个线程安全地调用,则此函数是线程安全的。
- 测试操作系统是否支持线程安全函数的方法:
- 编译时,测试_POSIX_THREAD_SAFE_FUNCTIONS。
- 运行时,sysconf函数传入_SC_THREAD_SAFE_FUNCTIONS参数。
- 造成线程不安全的情况:
- 函数返回的数据存放在静态的内存缓冲区中。
- *
2.3 线程私有数据
- 线程私有数据也叫线程特定数据(thread-specific data),是存储和查询某个特定线程相关数据的一种机制。
3. 相关函数
3.1 线程属性相关
pthread_attr_init和pthread_attr_destroy
- 原型:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
- 功能:
- init:初始化一个pthread_attr_t结构。
- destroy:反初始化pthread_attr_t结构。
- 参数:
- attr:指向pthread_attr_t 结构的指针。
- 注意:
- 使用pthread_attr_init后,attr指向的结构的内容就是当前操作系统的线程属性的默认值。
- 如果pthread_attr_inin**的实现**对属性对象的内存空间是动态分配的,则pthread_attr_destroy会释放内存空间。
pthread_attr_getdetachstate 和 pthread_attr_setdetachstate
- 原型:
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
- 功能:
- setdetachstate:设置pthread_attr_t结构的detachstate属性为PTHREAD_CREATE_DETACHED或者PTHREAD_CREATE_JOINABLE。
- getdetachstate:获取detachstate属性。(从attr到detachstate)
- 参数:
- attr:指向pthread_attr_t结构的指针。
- detachstate: 指向保存分离状态的指针变量。(获取到的状态存到此变量指向的内存中)
pthread_attr_getstack 和 pthread_attr_setstack
- 原型:
#include <pthread.h>
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr, size_t stacksize);
int pthread_attr_getstack(const pthread_attr_t *attr,
void **stackaddr, size_t *stacksize);
- 功能:设置/获取线程栈属性。
- 参数:
- attr:指向pthread_attr_t结构的指针。
- stackaddr:栈的最低内存地址,但并不一定是栈的开始位置。
- stacksize:栈大小。
- 返回值:成功0, 否则错误编号。
pthread_attr_getstacksize 和 pthread_attr_setstacksize
- 原型:
#include <pthread.h>
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
- 功能:设置/获取线程属性stacksize。
- 参数:略。
- 返回:成功0,否则错误编号。
pthread_attr_getguardsize 和 pthread_attr_setguardsize
- 原型:
#include <pthread.h>
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
- 功能:设置/获取警戒区域大小。(用以避免栈溢出,通常值为系统页大小)
- 返回:成功0, 否则错误编号。
- 注意:
- 当修改线程属性stackaddr时,会使警戒区无效,即guardsize==0。
- 如果guardsize线程属性被修改了,操作系统可能会把它取为页大小的整数倍。
3.2 同步属性
- 这个知识点不大理解。p345-p348
pthread_mutexattr_*(互斥量属性)
- 原型:
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *kind);
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_getrobust(const pthread_mutexattr_t *restrict
attr, int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,
int robust);
- 功能:
- init: 初始化。
- destroy: 反初始化。
- getpshared:获取进程共享属性。
- setpshared:设置进程共享属性。
- getrobust:获取健壮的互斥量属性的值。
- setrobust:设置健壮的互斥量属性的值。
- gettype:获取互斥量类型属性。
- settype:设置互斥量类型属性。
- 类型互斥量属性控制这互斥量的锁定特性。
- 互斥量类型行为:
互斥量 没有解锁时重新加锁 不占有时解锁 已解锁时解锁 PTHREAD_MUTEX_NORMAL 死锁 未定义 未定义 PTHREAD_MUTEX_ERRORCHECK 返回错误 返回错误 返回错误 PTHREAD_MUTEX_RECURSIVE 允许 返回错误 返回错误 PTHREAD_MUTEX_DEFAULT 未定义 未定义 未定义 * 不占有时解锁:一个线程解锁被另一个线程线程加锁的互斥量。
- 返回:成功0, 否则错误编号。
pthread_rwlockattr_*(读写锁属性)
- 原型:
#include <pthread.h>
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);
- 功能:
- getpshared:获取进程共享属性。(读写锁唯一支持的属性)
- setpshared:设置进程共享属性。
- 参数:略。
- 返回值:成功0, 否则错误编号。
pthread_condattr_(条件变量属性)
- Single UNIX Specification目前定义了条件变量的两个属性:进程共享属性和时钟属性。
- 原型:
#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(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);
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);
- 功能:
- getclock:获取可被用于pthread_cond_timedwait函数的时钟ID。(注意在使用pthread_cond_timedwait前需要用pthread_condattr_t对象对条件变量进程初始化)
- setclock:对时钟ID进行修改。
- 参数:
- clock_id:时钟ID。
- 返回:成功0, 否则错误编号。
pthread_barrierattr_*(屏障属性)
- 原型:
#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
- 功能:略。
- 参数:略。
- 返回:成功0,否则错误编号。
3.3 重入
lockfile相关
- 原型:
void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);
- 功能:锁定标准io的FILE。(注意这个锁是递归的,也就是可以多次锁定的)
- 参数:略
- 返回:
- ftrylockfile:成功0,若不能获取锁,返回非0值。
- 其他两个没有返回值。
unlocked的字符操作
为了处理每读写一个字符就获取锁和释放锁一次的开销,可以使用下面函数。(p356)
* 原型:(其实有好多这种函数,只列出书上列出的几个,其他man手册有)
#include <stdio.h>
int getc_unlocked(FILE *stream);
int getchar_unlocked(void);
int putc_unlocked(int c, FILE *stream);
int putchar_unlocked(int c);
- 功能:略。
- 参数:略。
- 返回:略。
3.4 线程私有数据
在分配线程特定数据之前,需要创建与该数据关联的键。
pthread_key相关
- 原型:
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destr_function)(void *));
int pthread_key_delete(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *pointer);
void * pthread_getspecific(pthread_key_t key);
- 功能:(p359和p360的两个创建键的方法要再学习)
- create:创建一个键,还未与线程私有数据值关联。(这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有(特定)数据地址关联。
- delete:取消键与线程私有(特定)数据值的关联关系。
- setspecific:把键和线程特定数据关联起来。
- getspecific:获取线程私有数据的地址。
- 参数:
- key:指向键的指针。(这是一个值-结果参数)
- destructor:析构函数,没有则设为NULL。(pthread_exit, 线程执行返回, 正常退出是会调用,exit/_exit/_Exit/abort不会调用,线程取消时,只有在最后的清理处理程序执行返回后,析构函数才会执行)
- pointer:指向需要关联的私有数据地址的指针。
- 返回:0或错误编号。
3.5 取消选项
- 有两个线程属性并没有包含在pthread_attr_t结构中:可取消状态和可取消类型。
- 这两个属性影响这线程在相应pthread_cancel函数调用时所呈现的行为。
- 可取消状态有:PTHREAD_CANCEL_DISABLE 和 PTHREAD_CANCEL_ENABLE。
- 可取消类型有:PTHREADCANEL_DEFERRED 和 PTHREAD_CANCEL_ASYNCHRONOUS。
- 推迟取消:在遇到取消点才能取消。(取消点在p362)
- 异步取消:可以在任意时候取消。
pthread_setcancelstate和pthread_setcanceltype
- 原型:
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);
- 功能:
- pthread_setcancelstate:修改可取消状态。
- pthread_setcanceltype:修改可取消类型。
- 参数:略。
- 返回:0或错误编号。
pthread_testcancel
- 原型:
#include <pthread.h>
void pthread_testcancel(void);
- 功能:添加自己的取消点。
3.8 线程和信号
- 每个线程都有自己的信号屏蔽字(但会继承父线程的),但信号的处理是进程中所有线程共享的。
- 意味着:某线程修改某信号的处理行为后,所有线程都共享这个处理行为的改变。
- 进程用sigprocmask函数来阻止信号发送。
- 线程用pthread_sigmask。
- 为避免错误,线程在调用sigwait**之前**,必须阻塞那些它正在等待的信号。(返回前恢复)
- 把信号发送给进程用:kill。
- 把信号发送给线程用:pthread_kill。
- 闹钟定时器是进程资源,并且所有的线程共享相同的闹钟。
- 如果一个信号的动作是终止进程,把此信号传递给某个线程仍然会杀死整个进程。
pthread_sigmask
- 原型:
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset);
- 功能:
- 设置屏蔽信号集(当set不为NULL时)。
- 获取屏蔽信号集(当set为NULL且oldset不为NULL时)。
- 参数:
- how:
- SIG_BLOCK:把信号机添加到线程信号屏蔽字中。
- SIG_SETMASK:用信号集替换线程的信号屏蔽字。
- SIG_UNBLOCK:从线程信号屏蔽字中移除信号集。
- set:目标操作信号屏蔽集。
- oldset:原来的信号屏蔽集。
- how:
sigwait
- 原型:
#include <signal.h>
int sigwait(const sigset_t *set, int *sig);
- 功能:等待一个或多个信号出现。
- 参数:
- set:指定线程等待的信号集。
- sig:返回时,sig指向的整数将包含发送信号的数量。(???)
- 返回:0或错误编号。
pthread_kill
- 原型:
#include <signal.h>
int pthread_kill(pthread_t thread, int sig);
- 功能:
- 发送信号给线程。
- sig==0时,检查线程是否存在。
3.9 线程和fork
- fork后,子进程会继承互斥量,读写锁,条件变量等。(具体见博客:待补)
- 如果父进程有一个以上线程,fork后,如果子进程接着不exec,就要清理锁状态。(子进程内部只有一个线程)
- 可多次调用pthread_atfork函数从而设置多套fork处理函数(疑问:多套如何工作?)。
- 使用多套时,处理程序的调用顺序是不同的:(这样可以允许多个模块注册它们自己的fork处理函数,而且可以保持锁的层次)
- parent和child 是以它们注册时的顺序进行调用。
- prepare的调用顺序则和它们注册时相反。
pthread_atfork
- 原型:
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void(*child)(void));
- 功能:通过此函数建立fork处理函数清除锁状态。(最多可安装3个清理锁的函数)
- 参数:
- prepare:由父进程fork创建子进程前调用,任务是获取父进程定义的所有锁。
- parent:fork创建子进程之后、返回之前在父进程上下文中调用,任务是对prepare中获取的所有锁进行解锁。
- child:在fork返回前在子进程上下文中调用,任务和parent一样。
- 注意:不会出现加锁一次解锁两次的情况,prepare获取的锁在fork之后就有了副本。
4. 拓展知识
- 对进程来说,虚地址空间的大小是固定的。
4.1 pthread_once
- 原型:
#include <pthread.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
- 功能:只进程一次初始化。
- 参数:
- once_contral:必须是一个非本地变量(如全局变量或静态变量)而且必须初始化为PTHREAD_ONCE_INIT。
5. 疑问
- 什么时进程共享属性?
- 控制着条件变量时可以被进程的国歌线程使用,还是可以被多个进程的线程使用。
- 值可为:PTHREAD_PROCESS_SHARED(多进程中的多线程可用) 和 PTHREAD_PROCESS_PRIVATE(初始化该屏障的进程内可以)。
- 还要再深入。
- *
6. 习题
12.1 在linux系统中运行图12-17,把输出结果重定向到文件,解释结果。
- 终端输出和重定向结果:
- 这是一个行缓冲和全缓冲的问题。
- 标准输出定向到终端时,是行缓冲,每次打印一行。
- 标准输出定向到文件时,是全缓冲,fork的时候,复制了缓冲区内容,故而有此结果(当缓冲区满或关闭文件的时候,冲洗缓冲区)
12.5 假设可以在一个程序中创建多个线程执行不同的任务,为什么还是可能会需要fork?
- 可能需要在程序中运行另一个程序(exec)
- 可能单个进程受到某些限制(如默认单进程最多能打开1024个文件)
7. 参考资料
- 《unix环境高级编程》第12章