本章学习如何控制线程行为的细节
本章涉及thread attributes(线程属性)、synchronization primitive attributes(同步原语属性)以及线程如何使得数据在相同process中保持私有。
二、Thread Limits
1. 如何查询thread limits?这些限制的所用是什么?
使用sysconf
能够查询到线程的限制信息。
使用这些limits能够帮助提升应用程序在不同操作系统实现上的移植性(portability
)
2. process创建的关键字(key)是什么?
线程由于共享进程的地址空间,因此global variable是共享的,而key用于每个线程享有一些私有数据。
三、Thread Attribute
3. 如何设置thread的属性?如何初始化?
pthread_attr_t
结构体用于修改属性
pthread_attr_init/destory 用于初始化/销毁 属性
4. init/destory属性的注意点
- 如果init是使用动态分配的空间,destory会释放该空间
- 我们不需要检查destory的返回值,因为只要初始化成功,destory就不会失败
5. pthread_attr_t is opaque to applications(对应用程序难以理解)
app不知道其内部结构能够提升app的可移植性
6. pthread_detach允许操作系统在thread exit时,回收资源(reclaim resource)
7. thread属性有几个?是哪些?
四个
8. pthread_attr_setdetachstate能设置detachstate线程属性
为两种值之一: PTHREAD_CREATE_DETACHED or PTHREAD_CREATE_JOINABLE
9. 支持stack属性是POSIX的可选项
编译器间可以使用 _POSIX_THREAD_ATTR_STACKADDR或者 _POSIX_THREAD_ATTR_STACKSIZE检测是否支持
运行期间用sysconf参数 _SC_THREAD_ATTR_STACKADDR or _SC_THREAD_ATTR_STACKSIZE检测
10. stack attr如何设置?
使用 pthread_attr_getstack/setstack设置
链接:
在用光(run out of)thread stacks的虚拟地址空间时,使用malloc or mmap
分配空间,然后用setstack设置。
11. stack attr的两个注意点
- 对进程,虚拟地址空间的数量是固定的,只有一个stack,stack的size不是问题
- 对于线程,线程需要共享同样数量的虚拟地址空间
12. 单独设置stack size
pthread_attr_getstacksize/setstacksize
在你想要修改stack size却不想分配thread stacks时很有用
13. guardsize attribute有什么用?如何设置
在stack空间之后有一块内存用于保护stack overflow的问题。这块内存的大小就是guardsize,默认被设置为PAGESIZE bytes
。如果stack pointer溢出到guard buffer
中,会产生错误,通常是signal
- guardsize = 0表示关闭该特性(feature)
- 如果我们改变
stackaddr
属性,意味着系统假设我们将要管理 stacks并且关闭guard buffers
guardsize使用pthread_attr_getguardsize/setguardsize
来设置
14. concurrency level属性的作用?设置方法和注意点
作用
控制用户层threads映射到kernel threads or processes的数量
设置
pthread_getconcurrency/setconcurrency
注意点
- getconcurrency返回当前concurrency level
- 如果操作系统控制concurrency level, getconcurrency会返回0
- 可以向set传入 level 0,则让系统自己决定。这种用法还能清除上次调用的影响。
四、Syschronization Attributes(同步属性)
15. mutex属性初始化和销毁
pthread_mutexattr_init/destory
使用pthread_mutexattr_t
结构体,其中process-shared
属性和type
属性很有用。
16. 存在一种机制允许独立的进程将内存的同一区域映射到它们独立的地址空间
17. 获取和设置process-shared attribute
pthread_mutexattr_getpshared/setpshared
该属性被设置为PTHREAD_PROCESS_PRIVATE
能使得多线程访问同样的synchronization object(同步实体)
18. type属性有哪些?如何设置和注意点
各个属性作用如下
属性 | 作用 |
---|---|
PTHREAD_MUTEX_NORMAL | don’t do any special error checking or deadlock detection. |
PTHREAD_MUTEX_ERRORCHECK | provides error checking. |
PTHREAD_MUTEX_RECURSIVE | allows the same thread to lock it multiple times without first unlocking it. A recursive mutex maintains a lock count and isn’t released until it is unlocked the same number of times it is locked. Thus, if you lock a recursive mutex twice and then unlock it, the mutex remains locked until it is unlocked a second time. |
PTHREAD_MUTEX_DEFAULT | Linux 3.2.0 maps this type to the normal mutex type |
设置方式
pthread_mutexattr_gettype/gettype
19. recursive mutex使用注意点
- 如果recursive mutex被lock多次,并且在
pthread_cond_wait
中,condition(条件)将永远不能被满足,因为pthread_cond_wait的unlock不能释放mutex - 只有在其他方法都不行时,才考虑使用recursive locks
- 12.4的example(p397)有使用recursive和不使用的例程讲解
20 sleep和nanosleep用于计时
21 读写锁属性的相关设置
与mutex类似,使用pthread_rwlockaddr_t
结构体
pthread_rwlockattr_init / destory
pthread_rwlockattr_getpshared / setpshared
22 条件变量属性的相关设置
使用pthread_condattr_t
结构体
pthread_condattr_init/destory
也支持process-shared attribute
pthread_condattr_getpshared / setpshared
五、Reentrancy
除了如下图的functions,定义在Single UNIX Specification中的functions都确保thread-safe
unistd.h中
_POSIX_THREAD_SAFE_FUNCTIONS
决定是否支持thread-safe, 或者在sysconf
中使用_SC_THREAD_SAFE_FUNCTIONS
下图是POSIX.1中非线程安全functions的替换版本
23 POSIX.1提供以thread-safe的方法操作FILE object
- 使用
flockfile
orftrylockfile
获取FILE object
相关联的锁 - 该锁是 recursive(可迭代的):在你已经获得它的时候再次请求它,但是不会出现死锁(deadlocking)
- 尽管lock的实际实现没有特定,它也需要所有标准IO routines(程序)操作
FILE object
如同内部调用flockfile/funlockfile
24 标准IO需要他们各自的lock,而以字符为单位I/O性能会下降很多,因此有unlock版本的标准IO
getchar_unlock/getc_unlock
puchar_unlock/putc_unlock
这四个函数只有在flockfile/ftrylockfile
和funlockfile
环绕时才被调用。否则会产生无法预料的结果(由于对数据的不同步访问)。
25 如何自定义reentrant函数?
- 首先各个线程使用各自的buffer
- 使用mutex来保证线程安全
我们可以使用读写锁
来允许同时对数据
的访问,但是该数据的并发调用不是很频繁,这样提升不了多少性能
26 我们使得函数线程安全并不代表对于signal handlers也是reentrant的
如果此时2个线程运行该函数,线程a已经获得mutex,此时产生信号打断该线程。那么线程b由于获得不了mutex会一直阻塞。
因此我们必须使用recursive mutex
来防止其他线程在我们查看数据结构时改变数据,也能防止signal handlers
产生的deadlock
。
六、Thread-Specific Data
27 线程私有数据的作用?
例如errno,很多地方都会调用。errno就是用thread-private date 实现。这样一线程中改变errno不会影响其他thread中errno的值。
28 进程中key是什么?如何创建?注意点?
线程由于共享进程的地址空间,因此全局数据共享,key用于每个线程共享一些私有数据。在使用thread-private data前需要先创建key
。
创建key:pthread_key_create
- thread-specific data key creation
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
所有线程都可以使用同一个key,但是不同多线程会将不同的thread-private data地址与key
联系起来。
- thread正常中止时如调用
pthread_exit or return
,destructor会被执行。 - thread通过exit,_exit,_Exit, abort或者其他异常终止时,
destructor
不会被调用 - destructor可以用来释放给
thread-specific data
分配的空间
29 进程可创建key的上限是:PTHREAD_KEYS_MAX
30 process会确保keys相关的thread-specific数据为null
会检测最多:PTHREAD_DESTRUCTOR_ITERATIONS
次
31 如何删除key?
pthread_key_delete
- 调用该函数不会调用
destructor
。会释放与key相关的thread-specific data的所有内存。
32 pthread_once是什么?
thread确保初始化程序只运行一次
pthread_once - dynamic package initialization
#include <pthread.h>
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
pthread_once_t once_control = PTHREAD_ONCE_INIT;
//Returns:0 if OK, error number on failure
once_control
必须是非局部变量,为global
或者static
变量并且初始化为PTHREAD_ONCE_INIT
33 如何绑定thread-private data?注意点?
获得和设置绑定在key上的数据
#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);
//Returns:thread-specific data value or NULL if no value has been associated with the key
int pthread_setspecific(pthread_key_t key, const void *value);
//Returns:0 if OK, error number on failure
注意点
- 我们使用