《APUE》读书笔记第十二章-线程控制

时间:2021-12-23 14:09:32

本章中,主要是介绍控制线程行为方面的内容,同时介绍了在同一进程中的多个线程之间如何保持数据的私有性以及基于进程的系统调用如何与线程进行交互.

一.线程属性

我们在创建线程的时候可以通过修改pthread_attr_t结构的值来修改线程的属性,将这些属性与创建的线程联系起来。调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性.

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
//两者的返回值都是:若成功则返回0,否则返回错误编号

如果在创建线程时就知道不需要了解线程的终止状态,则可以修改pthread_attr_t结构中的detachstate属性,让线程以分离状态启动。

int pthread_attr_getdetachstate(const pthread_attr_t *attr,int* detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
//若成功则返回0,否则返回错误编号

可以调用pthread_attr_getdetachstate函数获取当前线程的detachstate线程属性,第二个参数所指向的整数可以被设置为PTHREAD_CREATE_DETACHED,以分离状态启动。也可以设置为PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序可以获取线程的终止状态。

//以分离状态创建线程
int makethread(void* (*fn)(void*),void* arg){
int err; pthread_t tid;
pthread_attr_t attr; err=pthread_attr_init(&attr);
if(err!=){
return err;
}
err=pthread_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if(err==){
err=pthread_create(&tid,&attr,fn,arg);
}
pthread_attr_destroy(&attr);
return err;
}

POSIX.1定义了线程栈属性的一些操作接口,线程栈属性的查询和修改一般是通过新的函数pthread_attr_getstack和pthread_attr_setstack来进行.

int pthread_attr_getstack(const pthread_attr_t *attr,void ** stackaddr,
size_t *stacksize);
int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr,size_t *stacksize);
//两者的返回值都是:若成功则返回0,否则返回错误编号
int pthread_attr_getstacksize(const pthread_attr_t *attr,size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);
//若成功则返回0,否则返回错误编号 线程属性guardsize控制着线程末尾之后用以避免栈溢出的扩展内存的大小、
int pthread_attr_getguardsize(const pthread_attr_t* attr,size_t guardsize);
int pthread_attr_setguardsize(pthread_attr_t* attr,size_t guardsize); 并发控制着用户级线程可以映射的内核线程或进程的数目
int pthread_getconcurrency(void); /返回值:当前的并发度
int pthread_setconcurrency(int level);

二.同步属性

线程的同步对象也有属性,主要讨论的是互斥量、读写锁和条件变量的属性.

1.互斥量的属性

#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t* attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr);
//成功则返回0,否则返回错误编号

如果进程共享互斥量设置为PTHREAD_PROCESS_SHARED,从多个进程共享的内存区域中分配的互斥量可以用于这些进程的同步.

可以使用pthread_mutexattr_getshared函数查询pthread_mutexattr)t结构,得到它的进程共享属性,可以用pthread_mutexattr_setpshared函数修改进程共享的属性。

int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr,int pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
//成功则返回0,否则返回错误编号

进程共享互斥量属性设置为PTHREAD_PROCESS_PRIVATE时,允许pthread线程库提供更加有效的互斥量实现。

类型互斥量的属性:

PTHREAD_MUTEX_NORMAL类型是标准的互斥量类型,并不做任何特殊的错误检测或死锁检测。PTHREAD_MUTEX_ERRORCHECK互斥量类型提供错误检测。

PTHREAD_MUTEX_RECURSIVE互斥量类型允许同一线程在互斥量解锁之前对互斥量进行多次加锁,用一个递归互斥量维护锁的计数,在加锁和解锁次数不相同的情况下不会释放锁。

PTHREAD_MUTEX_DEFAULT类型可以用于请求默认语义。操作系统在实现它的时候可以把这种类型*地映射到其他类型。

用来获取互斥量类型
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr,int *type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);

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* attr);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t* attr);

3.条件变量属性

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);

三.重入与线程私有数据

如果一个函数在同一时刻可以被多个线程安全地调用,我们就称为这个函数是线程安全的.很多函数它们并不是线程安全的,因为它们返回的数据时存放在静态的内存缓存区内,通   过修改接口,要求调用者自己提供缓存区可以使线程变为线程安全的.例如:acstime_r、gmtime_r等,在名字最后加了_r,以表明这个函数是可重入的 。

线程私有数据时存储和查询与某个线程相关的数据俄一种机制,希望每个线程可以独立地访问数据副本,而不需要担心与其他线程的同步访问问题。

在分配线程私有数据之前,需要创建与该数据关联的键。这个键用于获取对线程私有数据的访问权。使用pthread_key_create创建一个键.

int pthread_key_create(pthread_key_t* keyp,void (*destructor)(void*));

int pthread_key_delete(pthread_key_t* key);

创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联,创建线程时,每个线程的数据地址设为null值。pthread_key_create可以选择关联该键的析构函数,当线程采用pthread_exit或者线程执行返回,正常退出时,析构函数就会被调用。但如果调用exit、_exit、_Exit、abort或出现其他非正常的退出时,就不会调用这个析构函数。

pthread_once_t initflag=PTHREA_ONCE_INIT;
int pthread_once(pthread_once_t *initflag,void (*initfn)(void));

initflag必须是一个非本地变量(即全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT.每个线程都调用pthread_once,系统能保证初始化例程initflag只被调用一次,即在系统首次调用pthread_once时。

键一旦创建,就可以通过调用pthread_setspecific函数把键和线程私有数据关联起来,可以通过pthread_getspecific函数获得线程私有数据的地址。

#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h> static pthread_key_t key;
static pthread_once_t init_done=PTHREAD_ONCE_INIT;
pthread_mutex_t env_mutex=PTHREAD_MUTEX_INITIALIZER; extern char** environ; static void thread_init(void){
pthread_key_create(&key,free);
} char* genenv(const char* name){
int i,len;
char* envbuf; pthread_once(&init_done,thread_init);
pthread_mutex_lock(&env_mutex); envbuf=(char*)pthread_getspecific(key);
if(envbuf==NULL){
envbuf=(char*)malloc(ARG_MAX);
if(envbuf==NULL){
pthread_mutex_unlock(&env_mutex);
return NULL;
}
pthread_setspecific(key,envbuf);
} len=strlen(name);
for(i=;environ[i]!=NULL;i++){
if((strncmp(name,environ[i],len)== && (environ[i][len]=='=')){
strcpy(envbuf,&environ[i][len+]);
pthread_mutex_unlock(&env_mutex);
return (envbuf);
}
}
pthread_mutex_unlock(&env_mutex);
return (NULL);
}

四.取消选项

线程属性有两个属性并没有包含在pthread_attr_t结构中,它们是可取消状态和可取消类型,这两个属性影响着线程在响应pthread_cancel函数调用时可呈现的行为.

可取消状态属性可以是PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE,线程可以通过调用pthread_setcancelstate修改它的可取消状态.

pthread_cancel调用并不等待线程终止,在默认情况下,线程在取消请求发出以后还是继续运行,直到线程到达某个取消点,取消点是线程检测是否被取消并按照请求进行动作的一个位置。

int pthread_setcancelstate(int state,int *oldstate);

void pthread_testcancel(void);

int pthread_setcanceltype(int type,int *oldtype);

五.线程与信号

每个线程都有自己的信号屏蔽字,但是信号的处理程序是进程中所有线程共享的,这意味着尽管单个线程可以组织某些信号,但当线程修改了与某个信号相关的处理行为以后,所有线程都必须共享这个处理行为的改变。

int pthread_sigmask(int how,const sigset_t* set,sigset_t oset);

int sigwait(const sigset_t *set,int *signop);

如果信号集中的某个信号在sigwait调用的时候处于未决状态,那么sigwait将无阻塞地返回,在返回之前,sigwait将从进程中移除那些处于未决状态的信号。为了避免错误动作发生,线程在调用sigwait之前,必须阻塞那些它正在等待的信号。

int pthread_kill(pthread_t thread,int signo);