apue学习笔记(第十二章 线程控制)

本章将讲解控制线程行为方面的详细内容,而前面的章节中使用的都是它们的默认行为

线程属性

pthread接口允许我们通过设置每个对象关联的不同属性来细调线程和同步对象的行为。
管理这些属性的函数都遵循相同的模式:
1.每个对象与自己类型的属性对象进行关联(线程与线程属性关联,互斥量与互斥量属性关联等)
2.有一个初始化函数,把属性设置为默认值
3.有一个销毁属性对象的函数
4.每个属性都有一个从属性对象中获取属性值的函数
5.每个属性都有一个设置属性值的函数

下面是线程属性pthread_attr_t的初始化跟销毁函数

#include <pthread.h>
int pthread_attr_t_init(pthread_attr_t *attr);
int pthread_attr_t_destroy(pthread_attr_t *attr);

下图总结了POSIX.1定义了线程的属性

每个属性都有相应的获取跟设置函数
如果在创建线程时不需要了解线程的终止状态,就可以修改pthread_attr_t结构中detachstate线程属性,让线程一开始就处于分离状态
detachstate可以设置成两个合法值之一: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);

下面给出一个以分离状态创建线程的函数

 1 #include "apue.h"
 2 #include <pthread.h>
 3
 4 int
 5 makethread(void *(*fn)(void *), void *arg)
 6 {
 7     int                err;
 8     pthread_t        tid;
 9     pthread_attr_t    attr;
10
11     err = pthread_attr_init(&attr);
12     if (err != 0)
13         return(err);
14     err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
15     if (err == 0)
16         err = pthread_create(&tid, &attr, fn, arg);
17     pthread_attr_destroy(&attr);
18     return(err);
19 }

同样的,可以使用下面函数对线程栈属性进行管理

#include <pthread.h>
int pthread_attr_t_getstack(const pthread_attr_t *restrict attr,void **restrict stackaddr,size_t *restrict stacksize);
int pthread_attr_t_setstack(pthread_attr_t *attr,void *stackaddr,size_t stacksize);

可以使用下面函数来设置和读取线程属性stacksize

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

线程属性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);

同步属性

跟线程具有属性一样,线程的同步对象也有属性。本节将讨论互斥量属性、读写锁属性、条件变量属性和屏障属性。

互斥量属性

初始化跟反初始化函数

#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

值得注意的3个属性是:进程共享属性、健壮属性和类型属性。

下面两个函数用于设置跟获取进程共享属性

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

pshared可以设置为两个值:PTHREAD_PROCESS_PRIVATE(默认),PTHREAD_PROCESS_SHARED(从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些线程的同步)

下面两个函数用于设置跟获取健壮属性

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

健壮性属性取值有两种可能:

默认是PTHREAD_MUTEX_STALLED,这意味着持有互斥量的进程终止时不需要采取特别的动作;

另一个取值是PTHREAD_MUTEX_ROBUST,这个值导致线程调用pthread_mutex_lock获取锁,而该锁被另一个进程持有,但它终止时并没有对该锁进行解锁,此时线程会阻塞,从pthread_mutex_lock返回的值是EOWNERDEAD而不是0。

使用下面两个函数可以设置跟获取互斥量类型属性

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

读写锁属性

初始化跟反初始化函数

#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

读写锁支持的唯一属性是进程共享属性。它与互斥量的进程共享属性是相同的。

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

条件变量属性

有一对函数用于初始化和反初始化条件变量属性

#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destory(pthread_condattr_t *attr);

目前定义了条件变量的两个属性:进程共享属性和时钟属性

与其他的同步属性一样,条件变量支持进程共享属性,下面两个函数用于设置跟获取进程共享属性

#include <phread.h>
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr,int *restrict pshared);
int pthread_condattr_setpshared(pthrea_condattr_t *attr,int *pshared);

时钟属性控制计算pthread_cond_timedwait函数的超时参数采用的是哪个时钟,合法值是下图列出的时钟ID

下面两个函数用于设置跟获取时钟属性

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

屏障属性

#include <pthread.h>
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);

目前定义的屏障属性只有进程共享属性,作用与其他同步对象一样

#include <pthread.h>
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr,int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,int pshared);

重入

如果一个函数在相同的时间点可以被多个线程安全地调用,就称该函数是线程安全的。下图列出POSIX.1中不能保证线程安全的函数。

支持线程安全函数的操作系统实现会在<unistd.h>中定义符号_POSIX_THREAD_SAFE_FUNCTIONS,

对POSIX.1中的一些非线程安全函数,它会提供可替代的线程安全版本。下面列出这些函数的线程安全版本。

POSIX.1还提供了以线程安全的方式管理FILE对象的方法。可以使用flockfile和ftrylockfile获取给定FILE对象关联的锁。

这个锁是递归的:当你占有这把锁的时候,还是可以再次获取该锁,而且不会导致死锁。

#include <stdio.h>
int ftrylockfile(FILE *fp);
void flockfile(FILE *fp);

void funlockfile(FILE *fp); 

下面显示了getenv的一个非可重入版本

 1 #include <limits.h>
 2 #include <string.h>
 3
 4 #define MAXSTRINGSZ    4096
 5
 6 static char envbuf[MAXSTRINGSZ];
 7
 8 extern char **environ;
 9
10 char *
11 getenv(const char *name)
12 {
13     int i, len;
14
15     len = strlen(name);
16     for (i = 0; environ[i] != NULL; i++) {
17         if ((strncmp(name, environ[i], len) == 0) &&
18           (environ[i][len] == ‘=‘)) {
19             strncpy(envbuf, &environ[i][len+1], MAXSTRINGSZ-1);
20             return(envbuf);
21         }
22     }
23     return(NULL);
24 }

如果两个线程同时调用这个函数,将会看到不一致的结果。

下面演示getenv的可重入版本

 1 #include <string.h>
 2 #include <errno.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5
 6 extern char **environ;
 7
 8 pthread_mutex_t env_mutex;
 9
10 static pthread_once_t init_done = PTHREAD_ONCE_INIT;
11
12 static void
13 thread_init(void)
14 {
15     pthread_mutexattr_t attr;
16
17     pthread_mutexattr_init(&attr);
18     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
19     pthread_mutex_init(&env_mutex, &attr);
20     pthread_mutexattr_destroy(&attr);
21 }
22
23 int
24 getenv_r(const char *name, char *buf, int buflen)
25 {
26     int i, len, olen;
27
28     pthread_once(&init_done, thread_init);
29     len = strlen(name);
30     pthread_mutex_lock(&env_mutex);
31     for (i = 0; environ[i] != NULL; i++) {
32         if ((strncmp(name, environ[i], len) == 0) &&
33           (environ[i][len] == ‘=‘)) {
34             olen = strlen(&environ[i][len+1]);
35             if (olen >= buflen) {
36                 pthread_mutex_unlock(&env_mutex);
37                 return(ENOSPC);
38             }
39             strcpy(buf, &environ[i][len+1]);
40             pthread_mutex_unlock(&env_mutex);
41             return(0);
42         }
43     }
44     pthread_mutex_unlock(&env_mutex);
45     return(ENOENT);
46 }

线程特定数据

线程特定数据,也称为线程私有数据,是存储和查询某个特定线程相关数据的一种机制。

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

#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));

创建的键存储在keyp指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。

创建新建时,每个线程的数据地址设为空值。

pthread_key_create可以为该键关联一个可选择的析构函数。当这个线程退出时,如果数据地址已经被置为非空值,那么析构函数就会被调用。

线程通常使用malloc为线程特定数据分配内存。析构函数通常释放已分配的内存。

对于所有的线程,我们都可以通过调用pthread_key_delete来取消键与线程特定数据值之间的关联关系。

#include <pthread.h>
int pthread_key_delete(pthread_key_t key);

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

#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key,const void *value);

下面演示使用线程特定数据来维护每个线程的数据缓冲区副本的getenv可重入实现。

 1 #include <limits.h>
 2 #include <string.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5
 6 #define MAXSTRINGSZ    4096
 7
 8 static pthread_key_t key;
 9 static pthread_once_t init_done = PTHREAD_ONCE_INIT;
10 pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER;
11
12 extern char **environ;
13
14 static void
15 thread_init(void)
16 {
17     pthread_key_create(&key, free);
18 }
19
20 char *
21 getenv(const char *name)
22 {
23     int        i, len;
24     char    *envbuf;
25
26     pthread_once(&init_done, thread_init);
27     pthread_mutex_lock(&env_mutex);
28     envbuf = (char *)pthread_getspecific(key);
29     if (envbuf == NULL) {
30         envbuf = malloc(MAXSTRINGSZ);
31         if (envbuf == NULL) {
32             pthread_mutex_unlock(&env_mutex);
33             return(NULL);
34         }
35         pthread_setspecific(key, envbuf);
36     }
37     len = strlen(name);
38     for (i = 0; environ[i] != NULL; i++) {
39         if ((strncmp(name, environ[i], len) == 0) &&
40           (environ[i][len] == ‘=‘)) {
41             strncpy(envbuf, &environ[i][len+1], MAXSTRINGSZ-1);
42             pthread_mutex_unlock(&env_mutex);
43             return(envbuf);
44         }
45     }
46     pthread_mutex_unlock(&env_mutex);
47     return(NULL);
48 }

取消选项

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

可取消状态属性可以是PTHREAD_CANCEL_ENABLE,也可以是PTHREAD_CANCLE_DISABLE。线程可以通过调用pthread_setcancelstate修改它的可取消状态。

#include <pthread.h>
int pthread_setcancelstat(int state,int *oldstate);

pthread_cancle调用并不等待线程终止。在默认情况下,线程在取消请求发出以后还是继续运行,知道线程达到某个取消点。

取消点是线程检查它是否被取消的一个位置,如果取消了,则按照请求行事。POSIX.1保证在线程调用如下列出的任何函数时,取消点就会出现。

线程启动时默认的可取消状态时PTHREAD_CANCEL_ENABLE。当状态设为PTHREAD_CANCLE_DISABLE时,对pthread_cancel的调用并不会杀死线程。

取消请求对这个线程来说还处于挂起状态,当取消状态再次变为PTHREAD_CANCLE_ENABLE时,线程将在下一个取消点上对所有的取消请求进行处理。

可以调用pthread_testcancel函数在程序中添加自己的取消点。

#include <pthread.h>
void pthread_testcancel(void);

上面描述的默认取消类型是推迟取消。可以通过调用pthread_setcanceltype来修改取消类型。

#include <pthread.h>
int pthread_setcanceltype(int type,int *oldtype);

type参数可以是PTHREAD_CANCEL_DEFERRED(默认),也可以是PTHREAD_CANCEL_ASYNCHRONOUS(异步取消)。

如果使用异步取消。线程可以在任意时间撤销,而不是遇到取消点才能被取消。

线程和信号

信号的处理时进程中所有线程共享的。如果一个线程选择忽略某个给定信号,那么另一个线程就可以通过以下两种方式撤销上述线程的信号选择:

恢复信号的默认处理行为,或者为信号设置一个新的信号处理程序。

进程中的信号是递送给单个线程的。如果一个信号与硬件故障相关,那么该信号一般会被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程。

第十章讨论了进程如何使用sigprocmask函数来阻止信号发送。而线程则必须使用pthread_sigmask。

#include <signal.h>
int pthread_sigmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);

pthread_sigmask函数与sigprocmask函数基本相同,不过pthread_sigmask失败时返回错误码,不再像sigprocmask函数那样设置errno并返回-1。

线程可以通过sigwait等待一个或多个信号的出现。

#include <signal.h>
int sigwait(const sigset_t *restrict set,int *restrict signop); //发送信号的数量

要把信号发送给线程,可以调用pthread_kill。

#include <signal.h>
int pthread_kill(pthread_t thread,int signo);

线程和fork

当线程调用fork时,就为整个子进程创建了整个进程地址空间的副本,子进程会从父进程那儿继承了每个互斥量、读写锁和条件变量的状态。

要清除锁状态,可以通过调用pthread_atfork函数建立fork处理程序。

#include <pthread.h>
int pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));

prepare处理程序在父进程fork创建子进程前调用,作用是获取父进程定义的所有锁。

parent处理程序是创建子进程之后、返回之前在父进程上下文中调用的,作用是对获取的所有锁进行解锁。

child处理程序在fork返回之前在子进程上下文中调用,跟parent处理程序一样,作用是对获取的所有锁进行解锁。

下面程序描述了如何使用它pthread_atfork和fork处理程序

 1 #include "apue.h"
 2 #include <pthread.h>
 3
 4 pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
 5 pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
 6
 7 void
 8 prepare(void)
 9 {
10     int err;
11
12     printf("preparing locks...\n");
13     if ((err = pthread_mutex_lock(&lock1)) != 0)
14         err_cont(err, "can‘t lock lock1 in prepare handler");
15     if ((err = pthread_mutex_lock(&lock2)) != 0)
16         err_cont(err, "can‘t lock lock2 in prepare handler");
17 }
18
19 void
20 parent(void)
21 {
22     int err;
23
24     printf("parent unlocking locks...\n");
25     if ((err = pthread_mutex_unlock(&lock1)) != 0)
26         err_cont(err, "can‘t unlock lock1 in parent handler");
27     if ((err = pthread_mutex_unlock(&lock2)) != 0)
28         err_cont(err, "can‘t unlock lock2 in parent handler");
29 }
30
31 void
32 child(void)
33 {
34     int err;
35
36     printf("child unlocking locks...\n");
37     if ((err = pthread_mutex_unlock(&lock1)) != 0)
38         err_cont(err, "can‘t unlock lock1 in child handler");
39     if ((err = pthread_mutex_unlock(&lock2)) != 0)
40         err_cont(err, "can‘t unlock lock2 in child handler");
41 }
42
43 void *
44 thr_fn(void *arg)
45 {
46     printf("thread started...\n");
47     pause();
48     return(0);
49 }
50
51 int
52 main(void)
53 {
54     int            err;
55     pid_t        pid;
56     pthread_t    tid;
57
58     if ((err = pthread_atfork(prepare, parent, child)) != 0)
59         err_exit(err, "can‘t install fork handlers");
60     if ((err = pthread_create(&tid, NULL, thr_fn, 0)) != 0)
61         err_exit(err, "can‘t create thread");
62
63     sleep(2);
64     printf("parent about to fork...\n");
65
66     if ((pid = fork()) < 0)
67         err_quit("fork failed");
68     else if (pid == 0)    /* child */
69         printf("child returned from fork\n");
70     else        /* parent */
71         printf("parent returned from fork\n");
72     exit(0);
73 }

线程与I/O

第三章介绍了pread函数和pwrite函数,它们使偏移量和数据的读写成为一个原子操作。

时间: 2024-08-06 00:41:25

apue学习笔记(第十二章 线程控制)的相关文章

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

本章中,主要是介绍控制线程行为方面的内容,同时介绍了在同一进程中的多个线程之间如何保持数据的私有性以及基于进程的系统调用如何与线程进行交互. 一.线程属性 我们在创建线程的时候可以通过修改pthread_attr_t结构的值来修改线程的属性,将这些属性与创建的线程联系起来.调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性. #include <pthread.h> int pthread_attr_init(pthrea

Java学习笔记—第十二章 Java网络编程入门

第十二章  Java网络编程入门 Java提供的三大类网络功能: (1)URL和URLConnection:三大类中最高级的一种,通过URL网络资源表达方式,可以很容易确定网络上数据的位置.利用URL的表示和建立,Java程序可以直接读入网络上所放的数据,或把自己的数据传送到网络的另一端. (2)Socket:又称"套接字",用于描述IP地址和端口(在Internet中,网络中的每台主机都有一个唯一的IP地址,而每台主机又通过提供多个不同端口来提供多种服务).在客户/服务器网络中,当客

《JAVA编程思想》学习笔记——第十二章 通过异常处理错误

Java的基本理念是 "结构不佳的代码不能运行" 发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前.然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决.这就需要错误源能通过某种方式,把适当的信息传递给某个接收者----该接收者将知道如何正确处理这个问题. 异常情形是指阻止当前方法或作用域继续执行的问题. 当抛出异常后,有几件事会随之发生.首先,同Java中其它对象的创建一样,将使用new在堆上创建异常对象.然后,当前的执行路径被终止,并且从当前环境中弹出对异常对

SaltStack 学习笔记 - 第十二篇: SaltStack Web 界面

SaltStack 有自身的用python开发的web界面halite,好处是基于python,可以跟salt的api无缝配合,确定就比较明显,需要个性化对web界面进行定制的会比较麻烦,如果喜欢体验该界面的可以参考下面的文章  http://rfyiamcool.blog.51cto.com/1030776/1275443/ 我是运用另一个python+php来进行web开发,具体需要的工具有在我的另一篇文章里面介绍过,这里再重新进行整个开发介绍 首先介绍php 跟python通信的工具 pp

马哥学习笔记三十二——计算机及操作系统原理

缓存方式: 直接映射 N路关联 缓存策略: write through:通写 write back:回写 进程类别: 交互式进程(IO密集型) 批处理进程(CPU密集型) 实时进程(Real-time) CPU: 时间片长,优先级低IO:时间片短,优先级高 Linux优先级:priority 实时优先级: 1-99,数字越小,优先级越低 静态优先级:100-139,数据越小,优先级越高 实时优先级比静态优先级高 nice值:调整静态优先级   -20,19:100,139   0:120 ps

APUE学习笔记:第七章 进程环境

7.1 引言 本章将学习:当执行程序时,其main函数是如何被调用的:命令行参数是如何传送给执行程序的:典型的存储器布局是什么样式:如何分配另外的存储空间:进程如何使用环境变量:各种不同的进程终止方式等:另外还将说明longjmp和setjmp函数以及它们与栈的交互作用:还将介绍研究进程的资源限制 7.2 main函数 C程序总是从main函数开始执行.当内核执行C程序时,在调用main前先调用一个特殊的启动例程.可执行程序文件将此启动例程指定为程序的起始地址——这是由连接编辑器设置的,而连接编

汇编入门学习笔记 (十二)—— int指令、port

疯狂的暑假学习之  汇编入门学习笔记 (十二)--  int指令.port 參考: <汇编语言> 王爽 第13.14章 一.int指令 1. int指令引发的中断 int n指令,相当于引发一个n号中断. 运行过程相当于: (1)取中断类型吗n. (2)标志寄存器入栈:设置IF=0,TF=0. (3)CS.IP入栈 (4)(IP)=(n*4),(CS)=(n*4+2) 样例1:编写.安装中断7ch.实现求一个word型数据的平方,用ax存放这个数据. assume cs:code code s

汇编入门学习笔记 (十二)—— int指令、端口

疯狂的暑假学习之  汇编入门学习笔记 (十二)--  int指令.端口 参考: <汇编语言> 王爽 第13.14章 一.int指令 1. int指令引发的中断 int n指令,相当于引发一个n号中断. 执行过程相当于: (1)取中断类型吗n. (2)标志寄存器入栈:设置IF=0,TF=0. (3)CS,IP入栈 (4)(IP)=(n*4),(CS)=(n*4+2) 例子1:编写.安装中断7ch,实现求一个word型数据的平方,用ax存放这个数据. assume cs:code code seg

Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验

Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 这一章很多,但是很有趣,也是这书的最后一章知识点了,我现在还在考虑要不要写这个拼图和2048的案例,在此之前,我们先来玩玩Android5.X的新特性吧!