60 读书笔记:第12章 线程控制 (1)

12.3 线程属性

        可以使用pthread_addr_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(pthread_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定义的线程属性。虽然POSIX.1还为实时线程定义了额外的属性,但这里并不打算讨论这些属性。

        detachstate 线程的分离状态属性

        guardsize 线程栈末尾的警戒缓冲区大小(字节数)

        stackaddr 线程栈的最低地址

        stacksize 线程栈的大小(字节数)


        如果对现有的某个线程的终止状态不感兴趣,可以使用pthread_detach函数让操作系统在线程退出时收回它所占用的资源。

        如果在创建线程时就知道不需要了解线程的终止状态,则可以修改pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。可以使用pthread_addr_setdetachstate函数把线程属性detachstate设置为下面的两个合法值之一:设置为PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者设置为PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序可以获取线程的终止状态。

       #include <pthread.h>
       int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
       int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
        // 两者的返回值:若成功则返回0,否则返回错误编号。

        可以调用pthread_attr_getdetachstate函数获取当前的detachstate线程属性,第二个参数所指向的整数也许被设置为PTHREAD_CREATE_DETACHED,也可能被设置为PTHREAD_CREATE_JOINABLE,具体要取决于给定pthread_attr_t结构中的属性值。

        《UNIX环境高级编程》P315:程序清单12-1 以分离状态创建的线程

#include <pthread.h>
int makethread(void *(*fn)(void *), void *arg)
{
    int             err;
    pthread_t       tid;
    pthread_attr_t  attr;
    // 初始化线程属性结构pthread_attr_t
    err = pthread_attr_init(&attr); 
    if (err != 0)
        return(err);
        
    // 修改pthread_attr_t结构中的detachstate:以分离状态启动线程
    err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (err == 0)
        err = pthread_create(&tid, &attr, fn, arg); // 创建线程
    pthread_attr_destroy(&attr);                    // 对pthread_attr_t去初始化
    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,但它失败了。


        两个过时的线程属栈属性接口:pthread_attr_getstackaddr和pthread_attr_setstackaddr。线程栈属性的查询和修改一般是通过较新的函数pthread_attr_getstack和pthread_attr_setstack来进行的,这些新的函数消除了老接口定义中存在的二义性。

       #include <pthread.h>
       int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
       int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
// 两个函数的返回值:若成功则返回0,否则返回错误编号

        这两个函数可用于管理stackaddr线程属性,也可以用户管理statcksize线程属性。

        对于进程来说,虚拟地址空间的大小是固定的,进程中只有一个栈,所以它的大小通常不是问题。但对于线程来说,同样大小的虚拟地址空间必须被所有的线程栈共享。如果应用程序使用了太多的线程,致使线程栈的累计大小超过了可用的虚拟地址空间,这时就需要减少线程默认的栈大小。另一方面,如果线程调用的函数分配了大量的自动变量或者调用的函数涉及很深的栈帧,那么这时需要的栈大小可能要比默认的大。

        如果用完了线程栈的虚拟地址空间,可以使用malloc或者mmap来为其他栈分配空间,并用pthread_attr_setstack函数来改变新建线程的栈位置。线程栈所占内存范围中可寻址的最低地址可以由stackaddr参数指定,该地址与处理器结构相应的边界对齐。

        stackaddr线程属性被定义为栈的内存单元的最低地址,但这并不必然是栈的开始位置。对于某些处理器结构来说,栈是从高地址向低地址方向伸展的,那么stackaddr线程属性就是栈的结尾而不是开始位置。

        应用程序也可以通过pthread_attr_getstacksize和pthread_attr_setstacksize函数读取或设置线程属性stacksize。

       #include <pthread.h>
       int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
       int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
// 两个函数的返回值:若成功则返回0,否则返回错误编号

        如果希望改变栈的默认大小,但又不想自己处理线程栈的分配问题,这时使用pthread_attr_setstacksize函数就非常有用。


        线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小。这个属性默认设置为PAGESIZE个字节。可以把guardsize线程属性设为0,从而不允许属性的这种特征行为发生:在这种情况下不会提供警戒缓冲区。同样的,如果对线程属性stackaddr作了修改,系统就会假设我们会自己管理栈,并使警戒缓冲区机制无效,等同于把guardsize线程属性设置为0。

       #include <pthread.h>
       int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
       int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
// 两个函数的返回值:若成功则返回0,否则返回错误编号

        如果guardsize线程属性被修改了,操作系统可能把它取为页大小的整数倍。如果线程的栈指针溢出到警戒区域,应用程序就可能通过信号接收到出错消息。

更多的线程属性

        线程还有其他的一些属性,这些属性并没有在pthread_attr_t结构中表达:

                (1) 可取消状态。

                (2) 可取消类型。

                (3) 并发度。

        并发度控制着用户级线程可以映射的内核线程或进程的数目。如果操作系统的实现在内核级的线程和用户线程之间保持一对一的映射,那么改变并发度并不会有什么效果,因为所有的用户级的线程都可能被调度到。但是,如果操作系统的实现让用户级线程到内核级线程或进程之间的映射关系是多对一的话,那么在给定时间内增加可运行的用户级线程数,可能会改善性能。pthread_setconcurrency函数可用于提示系统,表明希望的并发度。

       #include <pthread.h>
       int pthread_setconcurrency(int new_level);
// 返回值:当前的并发度
       int pthread_getconcurrency(void);
// 返回值:若成功则返回0,否则返回错误编号

        pthread_getconcurrency函数返回当前的并发度。如果操作系统当前正控制着并发度,那么pthread_getconcurrency将返回0.

        pthread_setconcurrency函数设定的并发度只是对系统的一个提示,系统并不保证请求的并发度一定会被采用。如果希望系统自己决定使用什么并发度,就把传入的参数level设为0。这样,应用程序调用level参数为0的pthread_setconcurrency函数,就可以撤销在之前level参数非零的pthread_setconcurrency调用所产生的作用。

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)