pthread_attr_t
pthread_create
函数创建线程,其中pthread_attr_t
是线程属性对象。如果要设置线程为默认属性,则该参数设为NULL
。pthread_attr_t
结构修改线程属性,通过pthread_attr_init
函数初始化pthread_attr_t
为默认属性值。通过pthread_attr_destroy
函数销毁线程属性对象int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
属性名称 | 说明 |
---|---|
detachstate | 线程的分离状态属性 |
guardsize | 线程栈末尾的警戒缓冲区大小(字节) |
stackaddr | 线程栈的最低地址 |
stacksize | 线程栈的最小长度(字节数) |
pthread_attr_setdetachstate
函数设置线程属性对象的detachstate
属性:
PTHREAD_CREATE_DETACHED
:以分离(detach
)状态启动线程PTHREAD_CREATE_JOINABLE
(默认):正常启动线程,应用程序可以获取线程的终止状态(通过pthread_join
函数)pthread_attr_getdetachstate
函数获取当前的detachstate
线程属性int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
#include "apue.h"
#include
int
makethread(void *(*fn)(void *), void *arg)
{
int err;
pthread_t tid;
pthread_attr_t attr;
/*初始化attr为默认属性值*/
err = pthread_attr_init(&attr);
if (err != 0)
return(err);
err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (err == 0)
err = pthread_create(&tid, &attr, fn, arg);
/*此例中忽略了pthread_attr_destroy函数的返回值。在这个实例中,我们对线程属性进行了合理的初始化,
因此pthread_attr_destroy应该不会失败。如果失败了,将难以清理,并可能造成少量的内存泄露*/
pthread_attr_destroy(&attr);
return(err);
}
sysconf(_SC_THREAD_ATTR_STACKADDR)
和sysconf(_SC_THREAD_ATTR_STACKSIZE)
来检查系统对线程栈属性的支持情况。pthread_attr_getstack
和pthread_attr_setstack
对线程栈属性进行获取/设置(即获取/设置线程栈的最低地址和大小)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);
32
位的操作系统,虚拟地址空间的大小为2^32B
即0
~4GB
的虚拟地址空间),因为进程中只有一个栈,所以它的大小通常不是问题。malloc
或mmap
来为可替代的栈分配空间,并用pthread_attr_setstack
函数来改变新建线程的栈位置。stackaddr
参数指定的地址用作线程栈的内存范围中最低可寻址地址,stacksize
为分配的缓冲区字节数。stackaddr
线程属性被定义为栈的最低内存地址,但这并不一定是栈的开始位置:如果进程内存空间中栈是从高地址向低地址方向增长的,那么stackaddr
将是栈的结尾地址而非开始地址。pthread_attr_getstacksize
和pthread_attr_setstacksize
读取/设置线程属性stacksize
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
pthread_attr_setstack
那样自己设置线程栈空间),可以使用这个函数。stacksize
不能小于PTHREAD_STACK_MIN
限制guardsize
线程属性控制着线程栈末尾之后用以避免栈溢出的扩展内存大小。4KB
)。guardsize
线程属性设置为0
,不允许属性的这种行为发生:此时不提供警戒缓冲区;同样,如果调用pthread_attr_setstack
修改线程stackaddr
属性,系统就认为我们自己管理栈,因此警戒缓冲区机制无效,等同于将guardsize
设为0
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);
guardsize
线程属性,操作系统可能会把它取为页的整数倍大小。$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7721
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7721
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
alloca
函数在栈上申请一个很大的空间的话,应该就会发生栈越界等内存异常的程序崩溃现象。即如果程序使用的栈内存超出最大值,就会发生栈溢出(Stack Overflow
)错误pthread_mutexattr_t
结构体表示互斥量属性对象。pthread_mutexattr_init
初始化互斥量属性对象,通过pthread_mutexattr_destroy
反初始化互斥量属性对象。int pthread_mutexattr_init (pthread_mutexattr_t *__attr);
int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr);
pthread_mutexattr_init
将pthread_mutexattr_t
初始化为默认的互斥量属性。pthread_mutexattr_t
互斥量属性对象包含三个属性:
pthread_mutexattr_getpshared
和pthread_mutexattr_setpshared
函数获取/修改pthread_mutexattr_t
结构:int pthread_mutexattr_getpshared (const pthread_mutexattr_t *attr,int *__restrict __pshared);
int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr,int __pshared);
PTHREAD_PROCESS_PRIVATE
(默认):该进程中的线程可以访问该互斥量对象PTHREAD_PROCESS_SHARED
:允许相互独立的多个进程把同一个内存数据块映射到它们各自的地址空间中,即从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些进程的同步(该互斥量可能位于在多个进程之间共享的共享内存对象中)。pthread_mutexattr_getrobust
和pthread_mutexattr_setrobust
获取/设置健壮属性值int pthread_mutexattr_getrobust (const pthread_mutexattr_t *__attr,int *__robustness);
int pthread_mutexattr_setrobust (pthread_mutexattr_t *__attr,int __robustness)
PTHREAD_MUTEX_STALLED
(默认):
PTHREAD_MUTEX_ROBUST
:
EOWNWERDEAD
值,新的拥有者应该再去调用pthread_mutex_consistent
来保持锁状态的一致性,并解锁。否则当这个锁解锁以后,该互斥量就不再可用,其他试图获得该互斥量的线程就不能拿到该锁并返回ENOTRECOVERABLE
。int pthread_mutex_consistent(pthread_mutex_t *mutex);
pthread_mutex_lock()
的 EOWNERDEAD
返回值通知。通过pthread_mutexattr_gettype
和pthread_mutexattr_settype
获取/修改互斥量类型属性
int pthread_mutexattr_settype (pthread_mutexattr_t *__attr, int __kind);
int pthread_mutexattr_gettype (const pthread_mutexattr_t * __attr, int * __kind)
类型属性表现了互斥量的锁定特性
PTHREAD_MUTEX_NORMAL
:
PTHREAD_MUTEX_ERRORCHECK
:
PTHREAD_MUTEX_RECURSIVE
:
PTHREAD_MUTEX_DEFAULT
:
PTHREAD_MUTEX_NORMAL
。func1
和func2
函数的接口不能改变。我们只能把互斥量嵌入式到数据结构中,把这个数据结构的地址(x)作为参数传入。func1
和func2
函数都必须操作这个结构,而且可能会有一个以上的线程同时访问该数据结构,那么func1
和func2
必须在操作数据以前对互斥量加锁,并且该互斥量应该是递归类型的,否则会出现死锁。func2
函数的私有版本,称之为func2_locked
函数,可以保持func1
和func2
函数接口不变,而且避免使用递归互斥量。func2_locked
函数,必须占有嵌入在数据结构中的互斥量,这个数据结构的地址是作为参数传入的。func2_locked
的函数体包括func2
的副本,func2
现在只是获取互斥量,调用func2_locked
,然后释放互斥量。#include "apue.h"
#include
#include
#include
extern int makethread(void *(*)(void *), void *);
struct to_info {
void (*to_fn)(void *); /* function */
void *to_arg; /* argument */
struct timespec to_wait; /* time to wait */
};
#define SECTONSEC 1000000000 /* seconds to nanoseconds */
#if !defined(CLOCK_REALTIME) || defined(BSD)
#define clock_nanosleep(ID, FL, REQ, REM) nanosleep((REQ), (REM))
#endif
#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME 0
#define USECTONSEC 1000 /* microseconds to nanoseconds */
void
clock_gettime(int id, struct timespec *tsp)
{
struct timeval tv;
gettimeofday(&tv, NULL);
tsp->tv_sec = tv.tv_sec;
tsp->tv_nsec = tv.tv_usec * USECTONSEC;
}
#endif
void *
timeout_helper(void *arg)
{
struct to_info *tip;
tip = (struct to_info *)arg;
/*线程在时间未到时将一直等待,时间到了以后再调用请求的函数*/
clock_nanosleep(CLOCK_REALTIME, 0, &tip->to_wait, NULL);
(*tip->to_fn)(tip->to_arg);
free(arg);
return(0);
}
/*该函数允许安排另一个函数在未来的某个时间运行*/
void
timeout(const struct timespec *when, void (*func)(void *), void *arg)
{
struct timespec now;
struct to_info *tip;
int err;
clock_gettime(CLOCK_REALTIME, &now);
if ((when->tv_sec > now.tv_sec) ||
(when->tv_sec == now.tv_sec && when->tv_nsec > now.tv_nsec)) {
tip = malloc(sizeof(struct to_info));
if (tip != NULL) {
tip->to_fn = func;
tip->to_arg = arg;
tip->to_wait.tv_sec = when->tv_sec - now.tv_sec;
if (when->tv_nsec >= now.tv_nsec) {
tip->to_wait.tv_nsec = when->tv_nsec - now.tv_nsec;
} else {
tip->to_wait.tv_sec--;
tip->to_wait.tv_nsec = SECTONSEC - now.tv_nsec +
when->tv_nsec;
}
/*我们使用makethread函数以分离状态创建线程。
因为传递给timeout函数的func函数参数在未来运行,
所以我们不希望一直空等待线程结束。*/
err = makethread(timeout_helper, (void *)tip);
if (err == 0)
return;
else
free(tip);
}
}
/*
* We get here if (a) when <= now, or (b) malloc fails, or
* (c) we can't make a thread, so we just call the function now.
*/
(*func)(arg);
}
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
/*需要把retry函数安排为原子操作,retry函数试图对同一个互斥量进行加锁,所以互斥量必须是递归的*/
void
retry(void *arg)
{
pthread_mutex_lock(&mutex);
/* perform retry steps ... */
pthread_mutex_unlock(&mutex);
}
int
main(void)
{
int err, condition, arg;
struct timespec when;
/*初始化互斥量属性对象*/
if ((err = pthread_mutexattr_init(&attr)) != 0)
err_exit(err, "pthread_mutexattr_init failed");
/*修改互斥量类型属性为PTHREAD_MUTEX_RECURSIVE,
允许同一线程在互斥量解锁之前多次加锁(即递归互斥量)*/
if ((err = pthread_mutexattr_settype(&attr,
PTHREAD_MUTEX_RECURSIVE)) != 0)
err_exit(err, "can't set recursive type");
/*使用属性attr初始化互斥量mutex*/
if ((err = pthread_mutex_init(&mutex, &attr)) != 0)
err_exit(err, "can't create recursive mutex");
/* continue processing ... */
/*timeout的调用者需要占有互斥量来检查条件,所以对互斥量mutex进行加锁*/
pthread_mutex_lock(&mutex);
/*
* Check the condition under the protection of a lock to
* make the check and the call to timeout atomic.
*/
if (condition) {
/*
* Calculate the absolute time when we want to retry.
*/
clock_gettime(CLOCK_REALTIME, &when);
when.tv_sec += 10; /* 10 seconds from now */
timeout(&when, retry, (void *)((unsigned long)arg));
}
pthread_mutex_unlock(&mutex);
/* continue processing ... */
exit(0);
}
pthread_rwlockattr_t
表示读写锁互斥量属性对象。
pthread_rwlockattr_init
和pthread_rwlock_desdroy
初始化/反初始化读写锁互斥量属性对象。int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr);
int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr);
int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t *__attr,int * __pshared);
int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr,int __pshared)
PTHREAD_PROCESS_SHARED
PTHREAD_PROCESS_PRIVATE
pthread_condattr_t
类型表示条件变量属性对象。
int pthread_condattr_init (pthread_condattr_t *__attr);
int pthread_condattr_destroy (pthread_condattr_t *__attr);
int pthread_condattr_getpshared (const pthread_condattr_t *__attr,int * __pshared);
int pthread_condattr_setpshared (pthread_condattr_t *__attr,int __pshared)
PTHREAD_PROCESS_SHARED
PTHREAD_PROCESS_PRIVATE
pthread_cond_timedwait
函数有用,表示其超时参数采用的是哪个时钟(clockid_t
类型),其时钟类型可以是以下类型:
CLOCK_REALTIME
:系统实时时间,即从1970年开始的时间CLOCK_MONOTONIC
:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响CLOCK_PROCESS_CPUTIME_ID
:本进程到当前代码的CPU时间CLOCK_THREAD_CPUTIME_ID
:本线程到当前代码的CPU时间int pthread_condattr_getclock (const pthread_condattr_t *__attr,clockid_t * __clock_id);
int pthread_condattr_setclock (pthread_condattr_t *__attr,clockid_t __clock_id)
pthread_barrierattr_t
表示屏障属性对象
int pthread_barrierattr_init (pthread_barrierattr_t *__attr);
int pthread_barrierattr_destroy (pthread_barrierattr_t *__attr);
int pthread_barrierattr_getpshared (const pthread_barrierattr_t *__attr,int *__restrict __pshared);
int pthread_barrierattr_setpshared (pthread_barrierattr_t *__attr,int __pshared);
PTHREAD_PROCESS_SHARED
PTHREAD_PROCESS_PRIVATE
多个控制线程在相同的时间有可能调用相同的函数,这种情况即为重入。
如果一个函数在相同的时间点可以被多个线程安全的调用,就称该函数是线程安全的。除了下图列出的函数,其他函数都是线程安全的。
对于一些非线程安全函数,会提供可替代的线程安全版本。这些函数的命名方式与它们的非线程安全版本的名字相似,只在最后加了_r
,表明这些版本是可重入的。
很多函数不是线程安全的,因为它们返回的数据存放在静态的内存缓冲区中(如静态局部变量)。在_r
函数中,通过修改接口,要求调用者自己提供缓冲区使函数变为线程安全的。
注意,如果一个函数对多个线程来说是可重入的,就说明这个函数是线程安全的。但是不能说明对信号处理程序来说该函数也是可重入的。
如果函数对异步信号处理程序的重入是安全的,那么就说函数是异步信号安全的(图10-4介绍的就是异步信号安全函数)
提供了以线程安全的方式管理FILE
对象的方法,通过flockfile
和ftrylockfile
获取与FILE
对象关联的锁,并且该锁是递归的。(规定所有操作FILE
对象的标准I/O
库函数的动作行为看起来就像是内部调用了flockfile
和funlockfile
)
void flockfile(FILE *filehandle);
int ftrylockfile(FILE *filehandle);
void funlockfile(FILE *filehandle);
根据上文规定,标准I/O库在操作FILE对象时都会获取对应的锁,这就导致在做一次一个字符的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);
注意,尽量不要使用这几个函数,它们可能由于多个线程非同步访问是数据而引起种种问题(因为没有获得锁,因此不是互斥的访问数据)。
实例:展示了getenv
(见7.9节)的一个可能实现,注意这个版本不是可重入的。
#include
#include
#define MAXSTRINGSZ 4096
static char envbuf[MAXSTRINGSZ];
extern char **environ;
char *
getenv(const char *name)
{
int i, len;
len = strlen(name);
for (i = 0; environ[i] != NULL; i++) {
if ((strncmp(name, environ[i], len) == 0) &&
(environ[i][len] == '=')) {
strncpy(envbuf, &environ[i][len+1], MAXSTRINGSZ-1);
return(envbuf);
}
}
return(NULL);
}
getenv
的线程返回的字符串都存储在同一个静态缓冲区中。实例:给出了getenv
的可重入版本,即getenv_r
。它使用pthread_once
函数来确保不管多少线程同时竞争getenv_r
,每个进程只调用thread_init
函数一次。(下一节会详细讲述pthread_once
函数)
#include
#include
#include
#include
extern char **environ;
pthread_mutex_t env_mutex;
static pthread_once_t init_done = PTHREAD_ONCE_INIT;
static void
thread_init(void)
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&env_mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
/*要使getenv_r可重入,需要改变接口,调用者必须提供自己的缓冲区,
这样每个线程可以使用各自不同的缓冲区避免其他线程的干扰*/
int
getenv_r(const char *name, char *buf, int buflen)
{
int i, len, olen;
pthread_once(&init_done, thread_init);
len = strlen(name);
/*需要在搜索请求的字符时保护环境不被修改,所以需要对互斥量加锁*/
pthread_mutex_lock(&env_mutex);
for (i = 0; environ[i] != NULL; i++) {
if ((strncmp(name, environ[i], len) == 0) &&
(environ[i][len] == '=')) {
olen = strlen(&environ[i][len+1]);
if (olen >= buflen) {
pthread_mutex_unlock(&env_mutex);
return(ENOSPC);
}
strcpy(buf, &environ[i][len+1]);
pthread_mutex_unlock(&env_mutex);
return(0);
}
}
pthread_mutex_unlock(&env_mutex);
return(ENOENT);
}
getenv_t
进行多次并发访问,但增加的并发性可能并不会在很大程度上改善程序的性能,因为:
getenv
和putenv
的调用不是频繁发生的。getenv_r
变成线程安全的,这也不意味着它对信号处理程序是可重入的。
getenv_r
就可能出现死锁。getenv_r
时中断了该线程,这时我们已经占有加锁的env_mutex
,这样其他线程试图对这个互斥量的加锁就会被阻塞,最终导致线程进入死锁状态。pthread
函数并不保证是异步信号安全的,所以不能把pthread
函数用于其他函数,让该函数成为异步信号安全。