ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] # 简介 Linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。 如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数 ~~~ typedef struct { int etachstate; //线程的分离状态 int schedpolicy; //线程调度策略 struct sched_param schedparam; //线程的调度参数 int inheritsched; //线程的继承性 int scope; //线程的作用域 size_t guardsize; //线程栈末尾的警戒缓冲区大小 int stackaddr_set; //线程的栈设置 void* stackaddr; //线程栈的位置 size_t stacksize; //线程栈的大小 } pthread_attr_t; ~~~ 主要结构体成员: 1) 线程分离状态 2) 线程栈大小(默认平均分配) 3) 线程栈警戒缓冲区大小(位于栈末尾) 4) 线程栈最低地址 属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread\_attr\_init,这个函数必须在pthread\_create函数之前调用。之后须用pthread\_attr\_destroy函数来释放资源。 线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。 # 线程属性初始化和销毁 ~~~ #include <pthread.h> ​ int pthread_attr_init(pthread_attr_t *attr); 功能: 初始化线程属性函数,注意:应先初始化线程属性,再pthread_create创建线程 参数: attr:线程属性结构体 返回值: 成功:0 失败:错误号 ​ int pthread_attr_destroy(pthread_attr_t *attr); 功能: 销毁线程属性所占用的资源函数 参数: attr:线程属性结构体 返回值: 成功:0 失败:错误号 ~~~ # 线程分离状态 线程的分离状态决定一个线程以什么样的方式来终止自己。 * 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread\_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。 * 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。 ~~~ #include <pthread.h> ​ int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 功能:设置线程分离状态 参数: attr:已初始化的线程属性 detachstate: 分离状态 PTHREAD_CREATE_DETACHED(分离线程) PTHREAD_CREATE_JOINABLE(非分离线程) 返回值: 成功:0 失败:非0 ​ int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); 功能:获取线程分离状态 参数: attr:已初始化的线程属性 detachstate: 分离状态 PTHREAD_CREATE_DETACHED(分离线程) PTHREAD _CREATE_JOINABLE(非分离线程) 返回值: 成功:0 失败:非0 ~~~ 这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread\_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread\_create的线程就得到了错误的线程号。 要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread\_cond\_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread\_create返回。 设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。 # 线程栈地址 POSIX.1定义了两个常量来检测系统是否支持栈属性: * \_POSIX\_THREAD\_ATTR\_STACKADDR * \_POSIX\_THREAD\_ATTR\_STACKSIZE 也可以给sysconf函数传递来进行检测: * \_SC\_THREAD\_ATTR\_STACKADDR * \_SC\_THREAD\_ATTR\_STACKSIZE 当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread\_attr\_setstack和pthread\_attr\_getstack两个函数分别设置和获取线程的栈地址。 ~~~ #include <pthread.h> ​ int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize); 功能:设置线程的栈地址 参数: attr:指向一个线程属性的指针 stackaddr:内存首地址 stacksize:返回线程的堆栈大小 返回值: 成功:0 失败:错误号 ​ int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize); 功能:获取线程的栈地址 参数: attr:指向一个线程属性的指针 stackaddr:返回获取的栈地址 stacksize:返回获取的栈大小 返回值: 成功:0 失败:错误号 ~~~ # 线程栈大小 当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。 ~~~ #include <pthread.h> ​ int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); 功能:设置线程的栈大小 参数: attr:指向一个线程属性的指针 stacksize:线程的堆栈大小 返回值: 成功:0 失败:错误号 ​ int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize); 功能:获取线程的栈大小 参数: attr:指向一个线程属性的指针 stacksize:返回线程的堆栈大小 返回值: 成功:0 失败:错误号 ~~~ # 例子 ~~~ #define SIZE 0x100000 ​ void *th_fun(void *arg) { while (1) { sleep(1); } } ​ int main() { pthread_t tid; int err, detachstate, i = 1; ​ pthread_attr_t attr; size_t stacksize; void *stackaddr; ​ pthread_attr_init(&attr); //线程属性初始化 pthread_attr_getstack(&attr, &stackaddr, &stacksize); //获取线程的栈地址 pthread_attr_getdetachstate(&attr, &detachstate); //获取线程分离状态 ​ if (detachstate == PTHREAD_CREATE_DETACHED) { printf("thread detached\n"); } else if (detachstate == PTHREAD_CREATE_JOINABLE) { printf("thread join\n"); } else { printf("thread unknown\n"); } pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置分离状态 ​ while (1) { stackaddr = malloc(SIZE); if (stackaddr == NULL) { perror("malloc"); exit(1); } ​ stacksize = SIZE; pthread_attr_setstack(&attr, stackaddr, stacksize); //设置线程的栈地址 err = pthread_create(&tid, &attr, th_fun, NULL); //创建线程 if (err != 0) { printf("%s\n", strerror(err)); exit(1); } printf("%d\n", i++); } ​ pthread_attr_destroy(&attr); //销毁线程属性所占用的资源函数 ​ return 0; } ~~~ # 线程使用注意事项 1) 主线程退出其他线程不退出,主线程应调用pthread\_exit 2) 避免僵尸线程 a) pthread\_join b) pthread\_detach c) pthread\_create指定分离属性 被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值; 3) malloc和mmap申请的内存可以被其他线程释放 4) 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程t在子进程中均pthread\_exit 5) 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制