http://www.cnblogs.com/nufangrensheng/p/3518411.html中介绍了pthread_create函数,并且当时的例子中,传入的参数都是空指针,而不是指向pthread_attr_t结构的指针。可以使用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来。可以使用pthread_attr_init函数初始化pthread_attr_t结构。调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。如果要修改其中个别属性的值,需要调用其他的函数。
#include <pthread.h> int pthread_attr_init( pthread_attr_t *attr ); int pthread_attr_destroy( pthtread_attr_t *attr ); 两个函数的返回值都是:若成功则返回0,否则返回错误编号
如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy将会释放该内存空间。除此之外,pthread_attr_destroy还会用无效的值初始化属性对象,因此如果该属性对象被误用,将会导致pthread_create函数返回错误。
pthread_attr_t结构对应用程序是透明的(摘自*:计算机术语,指客观存在并且运行着但是我们看不到的特性。客观存在的,但对于某些开发人员而言又不需要了解的东西,这就是计算机所指的透明性。简单来说,透明就是黑盒,你只需要应用它给出的接口,而不需要了解内在机理。),也就是说应用程序并不需要了解有关属性对象内部结构的任何细节,因而可以增强应用程序的可移植性。POSIX.1沿用了这种模型,并且为查询和设置每种属性定义了独立的函数。
表12-3 POSIX.1线程属性
名称 | 描述 |
detachstate | 线程的分离状态属性 |
guardsize | 线程栈末尾的警戒缓冲区大小(字节数) |
stackaddr | 线程栈的最低地址 |
stacksize | 线程栈的大小(字节数) |
注:每个线程都有自己独立的CPU上下文和栈,这两种资源是不能与其他线程共享的。
http://www.cnblogs.com/nufangrensheng/p/3519175.html中介绍了分离线程的概念。如果对现有的某个线程的终止状态不感兴趣的话,可以使用pthread_detach函数让操作系统在线程退出时收回它所占用的资源。
如果在创建线程时就知道不需要了解线程的终止状态,则可以修改pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。可以使用pthread_attr_setdetachstate函数把线程属性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 ); 两者的返回值都是:若成功则返回0,否则返回错误编号
可以调用pthread_attr_getdetachstate函数获取当前的detachstate线程属性,第二个参数所指向的整数用来保存获取到的detachstate属性值:PTHREAD_CREATE_DETACHED,或PTHREAD_CREATE_JOINABLE)。
实例
程序清单12-1 以分离状态创建的线程
#include "apue.h" #include <pthread.h> int makethread(void *(*fn)(void *), void *arg) { int err; pthread_t tid; pthread_attr_t attr; err = pthread_attr_init(&attr); if(err != 0) return(err); err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if(err == 0) err = pthread_create(&tid, &attr, fn, arg); pthread_attr_destroy(&attr); return(err); }
注意,这里忽略了pthread_attr_destroy函数调用的返回值。在这种情况下,由于对线程属性进行了合理的初始化,pthread_attr_destroy一般不会失败。但是如果pthread_attr_destroy确实出现了失败的情况,清理工作就会变得很困难:必须销毁刚刚创建的线程,而这个线程可能已经运行,并且与pthread_attr_destroy函数可能是异步执行的。忽略pthread_attr_destroy的错误返回可能出现的最坏的情况是:如果pthread_attr_init分配了内存空间,这些内存空间会被泄露。另一方面,如果pthread_attr_init成功地对线程属性进行了初始化,但pthread_attr_destroy在做清理工作时却出现了失败,就没有任何补救策略,因为线程属性结构对应用程序来说是透明的,可以对线程属性结构进行清理的唯一接口是pthread_attr_destroy,但它失败了。
对于遵循POSIX标准的操作系统来说,并不一定要支持线程栈属性,但是对于遵循XSI的系统,支持线程栈属性就是必须的。可以在编译阶段使用_POSIX_THREAD_ATTR_STACKADDR和_POSIX_ATTR_STACKSIZE符号来检查系统是否支持线程栈属性,如果系统定义了这些符号,就说明它支持相应的线程栈属性。也可以通过在运行阶段把_SC_THREAD_ATTR_STACKADDR和_SC_THREAD_ATTR_STACKSIZE参数传给sysconf函数,检查系统对线程栈属性的支持情况。
POSIX.1定义了线程栈属性的一些操作接口。线程栈属性的查询和修改一般是通过较新的函数pthread_attr_getstack和pthread_attr_setstack来进行。
#include <pthread.h> int pthread_attr_getstack( const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize ); int pthread_attr_setstack( const pthread_attr_t *attr, void *stackaddr, size_t *stacksize ); 两者的返回值都是:若成功则返回0,否则返回错误编号
这两个函数可以用于管理stackaddr线程属性,也可以用于管理stacksize线程属性。
对进程来说,虚拟地址的大小是固定的,进程中只有一个栈,所以它的大小通常不是问题。但对线程来说,同样大小的虚拟地址空间必须被所有的线程栈共享。如果应用程序使用了太多的线程,致使线程栈的累计大小超过了可用的虚拟空间,这时就需要减少线程默认的栈大小。另一方面,如果线程调用的函数分配了大量的自动变量或者调用的函数涉及很深的栈帧(stack frame),那么这时需要的栈大小可能要比默认的大。
如果用完了线程栈的虚拟地址空间,可以使用malloc或者mmap来为其他栈分配空间,并用pthread_attr_setstack函数来改变新建线程的栈位置。线程栈所占内存范围中可寻址的最低地址可以由stackaddr参数指定,该地址与处理器结构相应的边界对齐。
stackaddr线程属性被定义为栈的内存单元的最低地址,但这并不必然是栈的开始位置。对于某些处理器结构来说,栈是从高地址向低地址方向伸展的,那么stackadd线程属性就是栈的结尾而不是开始位置。
应用程序也可以通过pthread_attr_getstacksize和pthread_attr_setstacksize函数读取或设置线程属性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 ); 两者的返回值都是:若成功则返回0,否则返回错误编号
如果希望改变栈的默认大小,但又不想自己处理线程栈的分配问题,这时使用pthread_attr_setstacksize函数就非常有用。
线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线程属性设为0,从而不允许属性的这种特征行为发生:在这种情况下不会提供警戒缓冲区。同样地,如果对线程属性stackaddr作了修改,系统就会假设我们会自己管理栈,并使警戒栈缓冲区机制无效,等同于把guardsize属性设为0。
#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 ); 两者的返回值都是:若成功则返回0,否则返回错误编号
如果guardsize线程属性被修改了,操作系统可能把它取为页大小的整数倍。如果线程的栈指针溢出到警戒区域,应用程序就可能通过信号接收到出错信息。
更多的线程属性
线程还有其他的一些属性,这些属性并没有在pthread_attr_t结构中表达:
- 可取消状态。
- 可取消类型。
- 并发度。
并发度控制着用户级线程可以映射的内核线程或进程的数目(也就是说一个用户级线程可以分配到多少个内核级线程或进程进行并发处理)。如果操作系统的实现在内核级的线程和用户级的线程之间保持一对一的映射,那么改变并发度并不会有什么效果,因为所有的用户级的线程都可能被调度到。但是,如果操作系统的实现让用户级线程到内核级线程或进程之间的映射关系是多对一的话,那么在给定时间内增加可运行的用户级线程数,可能会改善性能。
pthread_setconcurrency函数可以用于提示系统,表明希望的并发度。
#include <pthread.h> int pthread_getconcurrency( void ); 返回值:当前的并发度 int pthread_setconcurrency( int level ); 返回值:若成功则返回0,否则返回错误编号
pthread_getconcurrency函数返回当前的并发度。如果操作系统当前正控制着并发度(即之前没有调用过pthread_setconcurrency函数),那么pthread_getconcurrency将返回0。
pthread_setconcurrency函数设定的并发度只是对系统的一个提示,系统并不保证请求的并发度一定会被采用。如果希望系统自己决定使用什么样的并发度,就把传入的参数level设为0。这样,应用程序调用level参数为0的pthread_setconcurrency函数,就可以撤销在这之前level参数非零的pthread_setconcurrency调用所产生的作用。
本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/。