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

来源:互联网 发布:apphtml源码 编辑:程序博客网 时间:2024/05/17 23:27

在上一章中学习了有关线程的基础知识,第十二章对线程的特性继续做进一步的研究。第十一章另一个方面的内容就是分析如何使用这些同步函数对线程进行同步,不过我之前对这部分内容学习较少,现在看也没看出个什么眉目来,等用到的时候再详细研究吧。

12.3 线程属性

这一小节主要讨论线程属性的相关内容。在这里先简单的总结一下,在上一章中已经介绍过的同步方法有:

  1. 互斥量
  2. 读写锁
  3. 条件变量
  4. 自旋锁
  5. 屏障

其中除了自旋锁以外,其他的几种同步手段在调用初始化函数时都有“attr(属性)”这一参数,同时每种类型其对应的属性类型也不相同。对于这些属性,也有一套属性操作函数对其值进行操作。

对于线程属性上述规律同样适用,pthread_create函数中用到的pthread_attr_t类型的属性可以使用以下函数进行初始化或销毁。

#include <pthread.h>extern int pthread_attr_init (pthread_attr_t *__attr) __THROW __nonnull ((1));extern int pthread_attr_destroy (pthread_attr_t *__attr)     __THROW __nonnull ((1));

可以使用pthread_attr_setdetachstate函数将线程两种状态之一:PTHREAD_CREATE_DETACHED,以分离状态启动线程;PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序可以获取线程的终止状态。pthread_attr_setdetachstate函数原型如下:

extern int pthread_attr_setdetachstate (pthread_attr_t *__attr,int __detachstate)     __THROW __nonnull ((1));

通过以下函数获取当前detachstate线程属性。

extern int pthread_attr_getdetachstate (const pthread_attr_t *__attr,int *__detachstate)     __THROW __nonnull ((1, 2));

由于不同的线程之间需要共享线程栈,因此可能造成栈资源的枯竭。这里分享一点有关于线程栈的相关知识:http://blog.csdn.net/gykimo/article/details/9132157

如果线程栈的虚地址空间用完了,可以使用malloc或mmap申请空间,并使用pthread_attr_setstack函数设置新建线程的线程栈位置。

extern int pthread_attr_setstack (pthread_attr_t *__attr, void *__stackaddr,  size_t __stacksize) __THROW __nonnull ((1));

使用pthread_attr_getstack函数可以读取属性中已经设置的线程栈的位置。

extern int pthread_attr_getstack (const pthread_attr_t *__restrict __attr,  void **__restrict __stackaddr,  size_t *__restrict __stacksize)     __THROW __nonnull ((1, 2, 3));

还可以通过以下两个函数读取或设置线程栈的大小:

extern int pthread_attr_setstacksize (pthread_attr_t *__attr,      size_t __stacksize)     __THROW __nonnull ((1));extern int pthread_attr_getstacksize (const pthread_attr_t *__restrict                      __attr, size_t *__restrict __stacksize)     __THROW __nonnull ((1, 2));

最后可以通过以下两个函数设置警戒缓冲区的大小:

extern int pthread_attr_setguardsize (pthread_attr_t *__attr,      size_t __guardsize)     __THROW __nonnull ((1));extern int pthread_attr_getguardsize (const pthread_attr_t *__attr,                      size_t *__guardsize)     __THROW __nonnull ((1, 2));

让我们上面提到的几种属性总结一下,请见下表:

名称描述相关操作函数detachstate线程的分离状态属性

pthread_attr_setdetachstate
pthread_attr_getdetachstate
guardsize线程栈末尾的警戒缓冲区大小(字节数)
pthread_attr_setstack
pthread_attr_getstack
stackaddr线程栈的最低地址
pthread_attr_setstacksize
pthread_attr_getstacksize
stacksize线程栈的最小长度(字节数)
pthread_attr_setguardsize
pthread_attr_getguardsize


12.4 同步属性

12.4.1 互斥量属性

互斥量属性使用pthread_mutexattr_t结构表示。可以使用常量PTHREAD_MUTEX_INITIALIZER初始化,或调用函数进行初始化,进行初始化与销毁工作的函数如下:

extern int pthread_mutexattr_init (pthread_mutexattr_t *__attr)     __THROW __nonnull ((1));extern int pthread_mutexattr_destroy (pthread_mutexattr_t *__attr)     __THROW __nonnull ((1));

互斥量共有三个属性可以设置,分别是进程共享属性、健壮属性以及类型属性。

进程共享属性是指:在多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于进程的同步。进程共享属性共有两个值:“PTHREAD_PROCESS_PRIVATE(线程间共享,默认属性)”、“PTHREAD_PROCESS_SHARED(进程间共享)”。

对于这一属性的查询与设置要通过专门的函数:

extern int pthread_mutexattr_getpshared (const pthread_mutexattr_t * __restrict __attr, int *__restrict __pshared)     __THROW __nonnull ((1, 2));extern int pthread_mutexattr_setpshared (pthread_mutexattr_t *__attr,                     int __pshared)     __THROW __nonnull ((1));

健壮属性是指:健壮属性的使用首先是在进程共享属性被设置为PTHREAD_PROCESS_SHARED的前提下,即进程间共享互斥量,此时若健壮属性被设置为PTHREAD_MUTEX_STALLED,则持有互斥量的进程终止后不需要采取特别的动作,在这种情况下,使用互斥量后的行为是未定义的,等待该互斥量解锁的应用程序会被有效的“拖住”。另一个取值是“PTHREAD_MUTEX_ROBUST”,在这种情况下,原先阻塞在pthread_mutex_lock的进程会得到锁并返回EOWNERDEAD。

对这一属性进行操作的函数如下:

extern int pthread_mutexattr_getrobust (const pthread_mutexattr_t *__attr,int *__robustness)     __THROW __nonnull ((1, 2));extern int pthread_mutexattr_setrobust (pthread_mutexattr_t *__attr,                    int __robustness)     __THROW __nonnull ((1));

如果出现了上面提到的情况(进程获得锁同时返回码为EOWNERDEAD),则需要调用以下函数对不一致的状态进行清除:

extern int pthread_mutex_consistent (pthread_mutex_t *__mutex)     __THROW __nonnull ((1));

如果互斥量的状态无法清除,在线程对互斥量解锁以后,该互斥量将处于永久不可用状态。

如果线程没有调用pthread_mutex_consistent就对互斥量进行了解锁,那么其他试图获取该互斥量的阻塞线程就会得到错误码ENOTRECOVERABLE。此时互斥量将不再可用。

最后一种属性:类型互斥量属性,该属性控制着互斥量的锁定特性,POSIX.1定义了4种类型。

互斥量类型描述PTHREAD_MUTEX_NORMAL标准互斥量类型,不做任何特殊的错误检查或死锁检测PTHREAD_MUTEX_ERRORCHECK提供错误检查PTHREAD_MUTEX_RECURSIVE允许同一线程在互斥量解锁之前对该互斥量进行多次加锁,PTHREAD_MUTEX_DEFAULT提供默认特性和行为

书中还给出了在不同情况下,4种不同互斥量的行为。

其中“不占用时解锁”是指一个线程对被另一个线程加锁的互斥量进行解锁的情况。“在已解锁时解锁”是指,当一个线程对已经解锁互斥量进行解锁会发生什么。

互斥量类型没有解锁时重新加锁不占用时解锁在已解锁时解锁PTHREAD_MUTEX_NORMAL死锁未定义未定义PTHREAD_MUTEX_ERRORCHECK返回错误返回错误返回错误PTHREAD_MUTEX_RECURSIVE允许返回错误返回错误PTHREAD_MUTEX_DEFAULT未定义未定义未定义


对互斥量进行操作的函数如下:

extern int pthread_mutexattr_gettype (const pthread_mutexattr_t *__restrict      __attr, int *__restrict __kind)     __THROW __nonnull ((1, 2));extern int pthread_mutexattr_settype (pthread_mutexattr_t *__attr, int __kind)     __THROW __nonnull ((1));

有关于同步和互斥的内容还可以参考以下两篇blog:

http://blog.csdn.net/goodluckwhh/article/details/8564319

http://www.thinksaas.cn/topics/0/347/347324.html

12.4.2 读写锁属性

读写锁仅支持进程共享属性,使用pthread_rwlockattr_t结构表示,通过一下函数进行操作:

extern int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr)     __THROW __nonnull ((1)); //初始化extern int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr)     __THROW __nonnull ((1)); //销毁extern int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t *                      __restrict __attr,                      int *__restrict __pshared)     __THROW __nonnull ((1, 2)); //获取共享属性extern int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr,                      int __pshared)     __THROW __nonnull ((1));  //设置共享属性

12.4.3 条件变量属性

条件变量具有两个属性,分别是进程共享属性与时钟属性。

初始化、销毁、获取进程共享属性当前值、设置进程共享属性当前值与上面的函数类似。

extern int pthread_condattr_init (pthread_condattr_t *__attr)     __THROW __nonnull ((1));extern int pthread_condattr_destroy (pthread_condattr_t *__attr)     __THROW __nonnull ((1));extern int pthread_condattr_getpshared (const pthread_condattr_t *                    __restrict __attr,                    int *__restrict __pshared)     __THROW __nonnull ((1, 2));extern int pthread_condattr_setpshared (pthread_condattr_t *__attr,                    int __pshared) __THROW __nonnull ((1));

时间属性控制计算pthread_cond_timedwait函数的超时参数采用的是哪个时钟。

extern int pthread_condattr_getclock (const pthread_condattr_t *      __restrict __attr,      __clockid_t *__restrict __clock_id)     __THROW __nonnull ((1, 2));extern int pthread_condattr_setclock (pthread_condattr_t *__attr,                      __clockid_t __clock_id)     __THROW __nonnull ((1));

12.4.4 屏障属性

屏障属性当前仅有进程共享属性,相关操作函数如下:

extern int pthread_barrierattr_init (pthread_barrierattr_t *__attr)     __THROW __nonnull ((1));extern int pthread_barrierattr_destroy (pthread_barrierattr_t *__attr)     __THROW __nonnull ((1));extern int pthread_barrierattr_getpshared (const pthread_barrierattr_t *                       __restrict __attr,                       int *__restrict __pshared)     __THROW __nonnull ((1, 2));extern int pthread_barrierattr_setpshared (pthread_barrierattr_t *__attr,                       int __pshared)     __THROW __nonnull ((1));

12.5 重入

如果一个函数在相同的时间点可以被多个线程安全的调用,就称该函数是线程安全的。如果一个函数对于多个线程来说是可重入的,则称这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。如果函数对异步信号处理程序的重入是安全的,则称该函数是异步信号安全。

有关于FILE对象锁的问题直接分享一篇blog吧:http://blog.csdn.net/crazy_programmer_p/article/details/37500411

12.6 线程特定数据

分配线程特定数据首先要创建与该数据相关联的键,该键用于获取对线程特定数据的访问。创建关联键的函数如下:

extern int pthread_key_create (pthread_key_t *__key,       void (*__destr_function) (void *))     __THROW __nonnull ((1));

创建的键存储在"__key"指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。除了创建键以外,pthread_key_create可以为该键关联一个可选择的析构函数。当线程调用pthread_exit或线程执行返回,正常退出时,析构函数就会被调用。同样,线程取消时,只有在最后的清理处理程序返回之后,析构函数才会被调用。如果线程调用exit、_exit、_Exit或abort,或者出现其他非正常的退出时,就不会调用析构函数。

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

可以通过调用pthread_once保证函数仅被线程调用一次。函数原型如下:

pthread_once_t __once_control = PTHREAD_ONCE_INITextern int pthread_once (pthread_once_t *__once_control, void (*__init_routine) (void)) __nonnull ((1, 2));

__once_control必须是非本地变量(如全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT。

可以通过pthread_setspcific函数把键和线程特定数据数据关联起来,可以通过pthread_getspcific函数获得线程特定数据的地址,函数原型如下:

extern int pthread_setspecific (pthread_key_t __key,const void *__pointer) __THROW ;extern void *pthread_getspecific (pthread_key_t __key) __THROW;

书中还给出了一个相关的例子,不过我认为该例子中使用的方式有些牵强,envbuf是临时变量,本来就是分配在线程栈的,已经具有线程特定数据这一属性,因此我认为书中想展示的用法并没有得到全面展示。对于这个问题还要通过实验进行验证。

有关于线程特定存储的内容再给大家分享两篇blog,内容都很不错

http://blog.chinaunix.net/uid-24774106-id-3651266.html

http://blog.csdn.net/dog250/article/details/7704898

12.7 取消选项

可取消状态属性可以是PTHREAD_CANCEL_ENABLE,也可以是PTHREAD_CANCEL_DISABLE。线程可以通过调用pthread_setcancelstate函数进行设定,函数原型如下:

extern int pthread_setcancelstate (int __state, int *__oldstate);

函数将原有的可取消状态存储在“__oldstate”。

线程被取消并不意味着立即终止,而是继续执行直到到达取消点。所谓取消点是线程检查它是否被取消的一个位置,如果取消了,则按照请求行事。

还可以通过函数定义取消点,函数原型如下:

extern void pthread_testcancel (void);

默认的取消类型为推迟取消,这种模式的工作方式是指:调用pthread_cancel以后,在线程到达取消点之前,并不会真正的取消。修改取消类型可通过以下函数:

extern int pthread_setcanceltype (int __type, int *__oldtype);

取消类型共有两种:PTHREAD_CANCEL_DEFERRED与PTHREAD_CANCEL_ASYNCHRONOUS。PTHREAD_CANCEL_ASYNCHRONOUS的属性是,线程可以在任意时间取消,不是非得遇到取消点才能取消。

12.8 线程和信号

每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。线程使用以下函数阻止信号递送:

#include <signal.h>extern int pthread_sigmask (int __how,    const __sigset_t *__restrict __newmask,    __sigset_t *__restrict __oldmask)__THROW;

实际定义位于/usr/include/i386-linux-gnu/bits/sigthread.h中

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

#include <signal.h>extern int sigwait (const sigset_t *__restrict __set, int *__restrict __sig)     __nonnull ((1, 2));

如果信号集中的某个信号在sigwait调用的时候处于挂起状态,那么sigwait将无阻塞的返回。在返回之前,sigwait将从进程中移除那些处于挂起等待状态的信号。如果具体实现支持排队信号,并且信号的多个实例被挂起,那么sigwait将会移除该信号的一个实例,其他的实例还要继续排队。sigwait函数会原子地取消信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait将恢复线程的信号屏蔽字。

如果多个线程在sigwait的调用中因等待同一个信号而阻塞,那么在信号递送的时候,就只有一个线程可以从sigwait返回(线程是否操作系统随机选定的)。如果一个信号被捕获(注册了信号处理函数),而且一个线程正在sigwait调用中等待同一个信号,则此时将由操作系统实现来决定以何种方式递送信号。

将信号发送给线程,可以调用pthread_kill,函数原型如下:

extern int pthread_kill (pthread_t __threadid, int __signo) __THROW;

最后给大家分享一篇blog,这篇blog中分析了线程与信号之间的关系:http://blog.chinaunix.net/uid-24774106-id-4065797.html


0 0