您的位置:首页 > 编程语言

使用pthread库进行多线程编程2 - UNIX高级环境编程第12章读书笔记

2007-12-14 19:06 731 查看
12 Thread Control

1 Thread Limits

用sysconf函数可以获得和thread相关的一些系统信息,主要是线程相关的一些最大值:
NAME Description Argument
PTHREAD_DESTRUCTOR_ITERATIONS 最大尝试销毁线程相关数据(Thread Specific Data)的次数,见下面关于Thread-Specific Data的内容 _SC_THREAD_DESTRUCTOR_ITERATIONS
PTHREAD_KEYS_MAX 一个进程所能够创建的最大键数 _SC_THREAD_KEYS_MAX
PTHREAD_STACK_MIN 线程栈的最小值 _SC_THREAD_STACK_MIN
PTHREAD_THREADS_MAX 单个进程中的线程个数最大值 _SC_THREAD_THREADS_MAX
 部分概念在后面会提到。虽然标准定义了这些常量,不过在很多系统上面可能根本就没有定义对应的Argument(如_SC_THREAD_DESTRUCTOR_ITERATIONS可能未定义),或者sysconf函数返回错误。因此在很多时候这些很难派上用场。

2 Thread Attributes

在前面讲到pthread_create等函数的时候,这些函数有一个参数pthread_attr_t。缺省情况下可以传NULL。但是如果想自己定义线程的相关属性的话,应该调用pthread_attr_init函数来定义:
#include   int pthread_attr_init(pthread_attr_t *attr);   int pthread_attr_destroy(pthread_attr_t *attr);   返回0表示正常,出错时返回错误值
pthread_attr_init函数负责初始化pthread_attr_t结构为缺省值。pthread_attr_destroy负责释放在pthread_attr_init函数调用时分配的内存,同时将pthread_attr的内容置为非法。如果要修改属性,需要调用其他函数来手动设置。基本的线程属性如下:
Name Description
detachstate detached状态,在前一章中有讲述
guardsize 线程栈底部的Guard缓冲区的大小
stackadddr 线程栈的最低地址
stacksize 线程栈的大小
1.     Detached State:一个线程如果出于Detached状态,说明此线程在退出的时候可以立刻释放其资源和对应的结束代码,从而无法使用pthread_join。可以用pthread_attr_setdetachedstate函数来设置Detach状态。传入PTHREAD_CREATE_DETACHED可以让线程启动的时候就处于Detached状态,而传入PTHREAD_CREATE_JOINABLE则是以通常状态启动线程
#include   int pthread_attr_getdetachedstate(const pthread_attr_t *restrict attr, int *detachstate);   int pthread_attr_setdetachedstate(pthread_attr_t *restrict attr, int detachstate);   返回0表示正常,出错时返回错误值
 2.     GuardSize:在线程栈的末尾有一个比较小的内存区域,这个内存区域是保护起来的,一旦栈发生overflow,系统立刻就会知道,发送一个Signal(Windows也有类似的功能,只不过是用于自动增长栈的大小)。缺省情况下这个大小正好是一个页=PAGESIZE。甚至可以用函数将该数值设置为0来禁止这个功能。如果我们修改了栈地址的话,系统会认为我们会自己处理Overflow的问题,因此也不会提供这个功能。调用pthread_attr_get_guardsize & pthread_attr_set_guardsize可以获得/设置这个值:
#include   int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);   int pthread_attr_setguardsize(pthread_attr_t *restrict attr, size_t guardsize);   返回0表示正常,出错时返回错误值
 3.     StackSize:线程可以自己设置栈的大小,用pthread_attr_getstacksize和pthread_attr_setstacksize:
#include   int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);   int pthread_attr_setguardsize(pthread_attr_t *restrict attr, size_t stacksize);   返回0表示正常,出错时返回错误值
 4.     StackAddr:当进程中线程过多的时候,有可能会栈空间不足。一个方案是用malloc或者nmap来分配新的内存,作为一个另外的栈,供线程使用。可以调用pthread_attr_setstack和pthread_attr_getstack来获得/设置:
#include   int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);   int pthread_attr_setstack(pthread_attr_t *restrict attr, void *stackaddr, size_t *stacksize)   返回0表示正常,出错时返回错误值
 除此之外,还有其他一些线程属性:1.     Cancellability State2.     Cancellability Type3.     Concurrency Level1和2会在第6节中讲述。Concurrency Level定义了用户模式线程和内核线程/进程之间的对应关系。如果具体操作系统实现是按照1对1,也就是一个用户模式线程对应一个内核模式线程的话,那么修改这个值没有作用。但是如果操作系统实现用少量内核模式线程/进程来模拟用户模式线程的话,那么修改这个值可能会提高或者降低程序和系统的性能。Level值并没有具体的意义,只是一个hint。Level=0表示让系统自动选择。函数原型如下:
#include   int pthread_attr_getconcurrency(void);   int pthread_attr_setconcurrency(int level);   返回0表示正常,出错时返回错误值
注意这个属性不是和具体线程相关的,而是系统级别的。

3 Synchronization Attributes

同步对象也有他们自己的Attributes。

3.1 Mutex Attributes

Mutex的属性类型为pthread_mutexattr_t。可以用pthread_mutexattr_init和pthread_mutexattr_destroy来创建和释放Mutex Attributes。类似的,pthread_mutexattr_init函数会将结构初始化为缺省值。
#include   int pthread_mutexattr_init(pthread_mutexattr_t *attr);   int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);   返回0表示正常,出错时返回错误值
 Mutex的属性有:1.     Process-Shared:指定Mutex是否为多个进程所共享。缺省值是PTHREAD_PROCESS_PRIVATE,即只有创建者进程才可以访问此Mutex。也可以设置为PTHREAD_PROCESS_SHARED,在多个进程之间共享。
#include   int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);   int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);   返回0表示正常,出错时返回错误值
 2.     Type:指定Mutex的类型。Mutex有下列类型:
Type 未释放锁的情况获得锁 未获得锁情况下释放锁 已释放锁的情况下再次释放 Description
PTHREAD_MUTEX_NORMAL 死锁 未定义 未定义 一般的Mutex
PTHREAD_MUTEX_ERRORCHECK 出错 出错 出错 加强错误检查
PTHREAD_MUTEX_RECURSIVE 允许 出错 出错 允许单个线程获得锁多次,需要多次释放,但是不能超过获得锁的次数。一般用来处理可重入的函数,见下面一章
PTHREAD_MUTEX_DEFAULT 未定义 未定义 未定义 完全没有错误检查
通过调用pthread_mutexattr_gettype & pthread_mutexattr_settype来获得/设置对应的type:
#include   int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);   int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);   返回0表示正常,出错时返回错误值
 

3.2 Reader-Writer Lock Attributes

类似Mutex Attributes,但是只支持Process Shared属性。

3.3 Condition Variable Attributes

类似Mutex Attributes,但是只支持Process Shared属性。

4 Reentrancy

1.     大部分Single UNIX Specification所定义的函数都是线程安全的,但是也有不少例外。实际使用的时候建议参考文档,确定函数是否是线程安全。2.     文件支持用ftrylockfile, flockfile, funlockfile来锁定文件访问。标准IO函数被要求必须调用在内部实现中调用flockfile, funlockfile。基于字符的部分IO函数具有非线程安全版本,以_unlocked结尾,如:getchar_unlocked, getc_unlocked, putchar_unlocked, putc_unlocked3.     书中提供了一个可重入的getenv_r实现。要点是:a.     用到了Recursive Mutex(使用pthread_mutexattr_settype函数调用设置)来保护自己和其他线程冲突(普通的Mutex就可以做到),同时允许重入(必须用Recursive Mutex)b.     要求调用者提供自己的buffer,而不是用静态全局变量envbuf来访问结果c.     使用pthread_once函数保证只调用一个初始化函数一遍,用于初始化Mutex(当然用其他方法也可以)

5 Thread-Specific Data

1.     Thread-Specific Data是一种很方便的将数据和线程联系起来的方法,在C Runtime中也大量用到Thread-Specific Data来维护线程相关的数据,一个典型的例子是errno:实际上errno是一个函数调用,返回和线程相关的错误值。Windows中有类似的机制,称为TLS (Thread Local Storage)2.     访问Thread-Specific Data需要使用Key。不同线程使用同一个key访问同一类型的数据(比如Errno),但是可以存放不同的值。Key的类型为pthread_key_t3.     用pthread_key_create函数创建key:
#include   int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *))   返回0表示正常,出错时返回错误值
创建好之后key对应的Thread-Specific Data为NULL。Destructor函数指针指定当pthread_key_t被删除的时候需要自动调用的函数,可以传NULL。参数值为TSD的具体值,必然非NULL。在线程正常退出时候,如return或者pthread_exit,当数据值为非NULL时候会调用。但是当线程非正常退出,如调用exit, _exit,  _Exit, abort或者其他非正常退出的时候,destructor不会被调用。一般情况下,这个destructor用来销毁用户用malloc为Thread-Specific Data分配的空间。注意:一般不应该用destructor来调用pthread_key_delete,因为delete对于一个key只用调一次,而destructor是对每个线程都调用的,前提是线程正常退出并且TSD不为NULLKey的总数量可能会有限制。可以用PTHREAD_KEYS_MAX来查询最大值。因为调用Destructor的时候这个Destructor可能又会创建新的Key,所以当线程退出的时候,调用Destructor的过程会反复继续直到没有key具有非NULL值或者次数到达最大值PTHREAD_DESTRUCTOR_ITERATIONS为止。这个值可以用sysconf获得。4.     用pthread_key_delete函数删除key:
#include   int pthread_key_delete(pthread_key_t *keyp)   返回0表示正常,出错时返回错误值
 注意,调用此函数不会导致Destructor被调用!5.     可以用pthread_once函数保证某个函数只被调一次,用法如下:
#include   pthread_once_t initflag = PTHREAD_ONCE_INIT;   int pthread_once(pthread_once_t *initflag, void (*initfn)(void));   返回0表示正常,出错时返回错误值
即使在多个线程中被调,pthread_once保证initfn在进程中只会调用一次。6.     pthread_getspecific和pthread_setspecific用于访问key所对应的Thread-Specific Data,就是一个void *指针:
#include   Void * pthread_getspecific(pthread_key_t key)   返回和key相关的当前线程的Thread-Specific Data   int pthread_setspecific(pthread_key_t key, const void *value)   返回0表示正常,出错时返回错误值
 

6 Cancel Options

除了上面介绍的Thread Attributes之外,还有两个Thread Attributes没有介绍,这两个均和pthread_cancel函数有关。1.     Cancelability State:表示允许或者禁止pthread_cancel调用。PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE分别对应允许和禁止(缺省情况下自然是允许)。调用pthread_setcancelstate来设置状态:
#include   int pthread_setcancelstate(int state, int *oldstate)   返回0表示正常,出错时返回错误值
注意:这个函数对当前线程有效,并非设置pthread_attr_t如果对一个线程调用pthread_cancel,线程会继续运行知道线程到达一个Cancellation Point,也就是可撤销点。POSIX.1定义了一些可以作为Cancellation Point的一些函数。 你也可以自己定义自己的Cancellation Point,通过调用pthread_testcancel:
#include   int pthread_testcancel(void)   返回0表示正常,出错时返回错误值
 2.     Cancelability Type:指定Cancel的类型。缺省情况下,行为正如我们之前所描述的那样,线程会运行到一个Cancellation Point再Cancel,称之为Deferred Cancellation,对应的常量为PTHREAD_CANCEL_DEFERRED。此外,还支持一种称为PTHREAD_CANCEL_ASYNCHRONOUS的类型。这种情况下,线程会立刻被Cancel,无需得到Cancellation Point。使用pthread_setcanceltype可以改变线程的CancelType属性属性
#include   int pthread_setcanceltype(int type, int *oldtype)   返回0表示正常,出错时返回错误值
注意:这个函数对当前线程有效,并非设置pthread_attr_t 

7 Threads and Signals

pthread_sigmask函数可以阻止Signal的发送:
#include   int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)   返回0表示正常,出错时返回错误值
 调用sigwait函数可以等待signal的产生:
#include   int sigwait(const sigset_t *restrict set, int *restrict signop);   返回0表示正常,出错时返回错误值
当signal是pending的情况下,调用sigwait会立刻返回并且把signal从pending list中移走,这样这个signal就不会被调用。为了避免这种行为,可以将pthread_sigmask和sigwait合用,首先用pthread_sigmask在signal产生之前阻止某个signal,然后用sigwait等待这个signal。Sigwait会自动Unblock这个signal,然后在等待结束之后恢复mask。调用pthread_kill可以给一个线程发送signal:
#include   int pthread_kill(pthread_t thread, int signo)   返回0表示正常,出错时返回错误值
 

8 Threads and fork

当线程调用fork的时候,整个进程的地址空间都被copy(严格来说是copy-on-write)到child。所有Mutex  / Reader-Writer Lock / Condition Variable的状态都被继承下来。子进程中,只存在一个线程,就是当初调用fork的进程的拷贝。由于不是所有线程都被copy,因此需要将所有的同步对象的状态进行处理。(如果是调用exec函数的话没有这个问题,因为整个地址空间被丢弃了)处理的函数是pthread_atfork:
#include   int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));   返回0表示正常,出错时返回错误值
 1.     Prepare:在fork创建child进程之前,在parent进程中调用。职责是:获得所有的锁2.     Parent:在fork创建child进程之后,但在fork调用返回之前,在parent进程中调用。职责是:释放在prepare中获得的所有的锁3.     Child:在fork创建child进程之后,在fork调用返回值钱,在child进程中调用。职责是:释放在prepare中获得的所有的锁。看起来child和Parent这两个handler做的是重复的工作,不过实际情况不是这样。由于fork会make一份进程地址空间的copy,所以parent和child是在释放各自的锁的copy 

9 Threads and I/O

因为文件指针的位置是和进程相关的,所以当不同线程调用lseek、read、write的时候容易造成问题。pread & pwrite函数可以用来解决这个问题。这两个函数会设置文件指针然后读写,作为一个原子操作。  

Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1528530
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: