您的位置:首页 > 产品设计 > UI/UE

[APUE chapter 12] 线程控制

2016-11-02 10:46 447 查看
作者: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:原来的信号屏蔽集。

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章

8. 相关下载

12章代码下载:

csdn:http://download.csdn.net/detail/i_scream_/9670764

github:https://github.com/isshe/2.Advanced_programming/tree/master/chapter_12
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息