线程限制
标准定义了一些与线程操作相关的一些限制。
这些限制的使用是为了增强应用程序在不同的操作系统实现之间的可移植性。
线程属性
对于每个线程,在创建时候可以指定其特定的属性,对于属性的管理遵循相同的模式:
- 每个对象与其自己类型的属性对象进行关联,比如线程与线程属性关联,互斥量与互斥量属性关联。
- 一个属性对象包含了多个属性值,属性对象对于应用程序来说是不透明的,应用程序不需要了解属性对象内部结构的详细细节,应用程序通过相应的函数接口来管理属性对象;
- 属性初始化函数,将属性设置为默认值;
- 属性销毁函数,将释放初始化函数分配了与属性对象关联的资源;
- 每个属性都有一个从属性对象中获取属性值的函数;
- 每个属性都有一个设置属性值的函数。
可以使用pthread_attr_t结构修改线程的默认属性,并将这些属性与特定的创建线程关联起来。pthread_attr_t结构中包含的就是操作系统实现支持的所有线程属性值。可以通过以下函数来初始化或者销毁线程属性:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
/*若成功,返回0,若出错,返回错误编码*/
标准支持的线程属性包括:
如果在创建线程时,就知道不需要了解线程的终止状态,则可以设置线程属性的detachstate属性值,让线程处于分离状态。如果线程处理分离状态,线程底层存储资源可以在线程终止时立即被收回。
detachstate属性值key设置为PTHREAD_CREATE_DETACHED,即以分离状态启动线程,或者设置为PTHREAD_CREATE_JOINABLE,即正常启动线程,应用程序可以获取线程的终止状态。
#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr,
int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int *detachstate);
/*若成功,返回0,若出错,返回错误编码*/
一个程序范例为:
#include "apue.h"
#include <pthread.h>
int makethread(void *(*fn)(void *), void *arg)
{
int err;
pthread_t tid;
pthread_attr_t 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);
}
对于进程来说,虚拟地址空间大小是固定的,进程中只有一个栈,栈的大小不是问题。但是对于线程来说,同样大小的虚拟地址空间必须被所有的线程栈共享。如果程序使用的线程很多,使得线程栈的累计使用大小超过了可用的虚拟地址空间,则需要减少默认的线程栈的大小。相反,如果线程调度的函数涉及很深的栈帧,则需要增加默认的线程栈的大小。
可以通过以下函数来实现对线程栈属性的管理:
#include <pthread.h>
int pthread_attr_getstack(const pthread_attr_t *restrict attr,
void **restrict stackaddr,
size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr, size_t stacksize);
/*若成功,返回0,若出错,返回错误编码*/
参数stackaddr指定的地址作为线程栈的内存范围值的最低可寻址地址。对于一个给定的处理器结构来说,如果栈是从高地址向低地址方向增长的,那么stackaddr线程属性将是栈的结尾地址,而不是开始地址。但是stackaddr被定义为栈的最低内存地址。
如果希望改变线程栈的大小,但是又不想自己处理线程栈的分配问题,则调用以下函数:
#include <pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,
size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
/*若成功,返回0,若出错,返回错误编码*/
注意,选择的stacksize不能小于PTHREAD_STACK_MIN。
线程属性guardsize控制着线程栈尾端之后可用以避免栈溢出的警戒缓冲区。如果线程的栈指针溢出到警戒区,程序可能通过信号接收到出错信息。这个属性的默认值由操作系统的具体实现来定义,通常为页的大小的整数倍。
如果将guardsize设置为0,即不提供警戒缓冲区。
控制线程属性guardsize的函数接口为:
#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,
size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
/*若成功,返回0,若出错,返回错误编码*/
互斥量属性
对于互斥量,也有对应的互斥量属性,互斥量属性是用pthread_mutexattr_t结构来表示。
对于设置非默认的互斥量属性,使用以下的接口函数:
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
/*若成功,返回0,若出错,返回错误编码*/
其中函数pthread_mutexattr_init将使用默认的互斥量属性初始化pthread_mutexattr_t结构。
值得注意的3个重要属性分别是进程共享属性、健壮属性与类型属性。
在进程中,多个线程可以访问同一个同步对象,这是默认的行为,在这样的情况下,进程共享属性设置为PTHREAD_PROCESS_PRIVATE。
但是,还会存在这样的机制:允许互相独立的多个进程将同一个内存数据块映射到他们各自独立的地址空间中。这样就像多个线程访问共享数据一样,多个进程访问共享数据也需要同步机制,防止竞争。在这种情况下,将进程共享属性设置为PTHREAD_PROCESS_SHARED,从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些进程之间的同步。
对于进程共享属性的查询与设置函数接口为:
#include <pthread.h>
int pthread_mutexattr_getpshared(const pthread_mutexattr_t
*restrict attr,
int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,
int pshared);
/*若成功,返回0,若出错,返回错误编码*/
互斥量的健壮属性与多个进程之间共享的互斥量有关。当持有互斥量的进程终止时,需要解决互斥量的状态恢复问题。
互斥量的健壮属性的获取与设置函数接口为:
#include <pthread.h>
int pthread_mutexattr_getrobust(const pthread_mutexattr_t
*restrict attr,
int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,
int robust);
/*若成功,返回0,若出错,返回错误编码*/
健壮属性的默认值是PTHREAD_MUTEX_STALLED,在这种默认属性值下,持有互斥量的进程终止时不需要采取特别的动作,继续使用互斥量的行为是未定义的,等待该互斥量解锁的进程将被继续阻塞。
健壮属性的另一个取值为PTHREAD_MUTEX_ROBUST,如果一个进程调用pthread_mutex_lock试图获取互斥量,但是该互斥量被另一个进程持有,进程终止时没有释放该互斥量,则等待该互斥量的进程将从pthread_mutex_lock获取到的返回值是EOWNERDEAD。进程通过这个返回值可知道发生了什么,若可能,可以采取一些恢复操作。
因此,使用PTHREAD_MUTEX_ROBUST的健壮属性的互斥量后,在试图调用pthread_mutex_lock获取锁时,可能存在三个返回值:不需要恢复的成功(0)、需要恢复的成功(EOWNERDEAD)、失败(错误编码)。
互斥量的类型属性控制着互斥量的锁定特性。标准定义了4种类型:
- PTHREAD_MUTEX_NORMAL:标准的互斥量类型,不做任何特殊的错误检查或者死锁检测;
- PTHREAD_MUTEX_ERRORCHECK:提供错误检查的互斥量类型;
- PTHREAD_MUTEX_RECURSIVE:允许同一个线程在互斥量解锁之前对该互斥量进行多次加锁,递归互斥量维护锁的计数,在解锁的次数与加锁的次数相同的情况下,才会释放锁。
- PTHREAD_MUTEX_DEFAULT:提供默认特性和行为的互斥量类型,与操作系统的具体实现有关,比如对于Linux,将这种类型映射为PTHREAD_MUTEX_NORMAL类型的互斥量。
类型属性的获取与设置函数接口为:
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t
*restrict attr,
int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,
int type);
/*若成功,返回0,若出错,返回错误编码*/
需要谨慎使用PTHREAD_MUTEX_RECURSIVE类型的互斥量。
读写锁属性
对于读写锁,可以使用函数接口初始化或者销毁属性:
#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
/*若成功,返回0,若出错,返回错误编码*/
读写锁支持的唯一属性是进程共享属性。与互斥量的进程共享属性是相同的。用于设置与读取读写锁的进程共享属性的函数接口为:
#include <pthread.h>
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t
*restrict attr,
int *restrict pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,
int pshared);
/*若成功,返回0,若出错,返回错误编码*/
条件变量属性
使用以下函数可以对条件变量属性进行初始化与反初始化:
#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
/*若成功,返回0,若出错,返回错误编码*/
标准定义了条件变量的两个属性:进程共享属性与时钟属性。
进程共享属性控制着条件变量是可以被单个进程的多个线程使用,还是可以被多个进程的线程使用。对于条件变量的进程共享属性获取与设置接口函数为:
#include <pthread.h>
int pthread_condattr_getpshared(const pthread_condattr_t
*restrict attr,
int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,
int *pshared);
/*若成功,返回0,若出错,返回错误编码*/
时钟属性控制计算pthread_cond_timedwait函数的超时参数tsptr采用的是哪个时钟。接口函数原型为:
#include <pthread.h>
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);
/*若成功,返回0,若出错,返回错误编码*/