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

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 <pthread.h>

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 <pthread.h>

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,系统立刻就会知道,发送一个SignalWindows也有类似的功能,只不过是用于自动增长栈的大小)。缺省情况下这个大小正好是一个页=PAGESIZE。甚至可以用函数将该数值设置为0来禁止这个功能。如果我们修改了栈地址的话,系统会认为我们会自己处理Overflow的问题,因此也不会提供这个功能。调用pthread_attr_get_guardsize & pthread_attr_set_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 *restrict attr, size_t guardsize);

返回0表示正常,出错时返回错误值

3. StackSize:线程可以自己设置栈的大小,用pthread_attr_getstacksizepthread_attr_setstacksize

#include <pthread.h>

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_setstackpthread_attr_getstack来获得/设置:

#include <pthread.h>

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 State

2. Cancellability Type

3. Concurrency Level

12会在第6节中讲述。

Concurrency Level定义了用户模式线程和内核线程/进程之间的对应关系。如果具体操作系统实现是按照11,也就是一个用户模式线程对应一个内核模式线程的话,那么修改这个值没有作用。但是如果操作系统实现用少量内核模式线程/进程来模拟用户模式线程的话,那么修改这个值可能会提高或者降低程序和系统的性能。Level值并没有具体的意义,只是一个hintLevel=0表示让系统自动选择。函数原型如下:

#include <pthread.h>

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_initpthread_mutexattr_destroy来创建和释放Mutex Attributes。类似的,pthread_mutexattr_init函数会将结构初始化为缺省值。

#include <pthread.h>

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

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

返回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_unlocked

3. 书中提供了一个可重入的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_t

3. pthread_key_create函数创建key

#include <pthread.h>

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

返回0表示正常,出错时返回错误值

创建好之后key对应的Thread-Specific DataNULL

Destructor函数指针指定当pthread_key_t被删除的时候需要自动调用的函数,可以传NULL。参数值为TSD的具体值,必然非NULL。在线程正常退出时候,如return或者pthread_exit,当数据值为非NULL时候会调用。但是当线程非正常退出,如调用exit, _exit, _Exit, abort或者其他非正常退出的时候,destructor不会被调用。一般情况下,这个destructor用来销毁用户用mallocThread-Specific Data分配的空间。注意:一般不应该用destructor来调用pthread_key_delete,因为delete对于一个key只用调一次,而destructor是对每个线程都调用的,前提是线程正常退出并且TSD不为NULL

Key的总数量可能会有限制。可以用PTHREAD_KEYS_MAX来查询最大值。

因为调用Destructor的时候这个Destructor可能又会创建新的Key,所以当线程退出的时候,调用Destructor的过程会反复继续直到没有key具有非NULL值或者次数到达最大值PTHREAD_DESTRUCTOR_ITERATIONS为止。这个值可以用sysconf获得。

4. pthread_key_delete函数删除key

#include <pthread.h>

int pthread_key_delete(pthread_key_t *keyp)

返回0表示正常,出错时返回错误值

注意,调用此函数不会导致Destructor被调用!

5. 可以用pthread_once函数保证某个函数只被调一次,用法如下:

padding-right: 5.4pt; padding-left: 5.4pt; border-left-color: #e0dfe3; border-bottom-color: #e0dfe3; padding-bottom: 0in; width: 621pt; border-
分享到:
评论
lovnet
  • 浏览: 1328397 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

你可能感兴趣的:(thread,多线程,编程,unix,读书)