线程属性的相关设置详解
- pthread_attr_t 结构体
线程属性结构如下:
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈尾警戒缓冲区大小
int stackaddr_set;
void * stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈的大小
}pthread_attr_t;
detachstate,线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join() 函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
通俗的说也就是:我们知道一般我们要等待(pthread_join)一个线程的结束,主要是想知道它的结束状态,否则等待一般是没有什么意义的!但是如果对一些线程的终止态我们压根就不想知道,那么就可以使用“分离”属性,那么我 们就无须等待管理,只要线程自己结束了,自己释放资源就可以。
detachstate有两种取值:
PTHREAD_CREATE_DETACHED: 分离状态启动
PTHREAD_CREATE_JOINABLE: 聚合状态启动线程
schedpolicy,表示新线程的调度策略,主要包括:
SCHED_OTHER(正常、非实时)
SCHED_RR(实时、轮转法)
SCHED_FIFO(实时、先入先出)
缺省为SCHED_OTHER,
schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,
/usr/include /bits/sched.hstruct sched_param{int sched_priority; //!> 参数的本质就是优先级};
注意:大的权值对应高的优先级!
系统支持的最大和最小的优先级值可以用函数:
sched_get_priority_max和sched_get_priority_min得到!
inheritsched,继承性决定调度的参数是从创建的进程中继承还是使用在
schedpolicy和schedparam属性中显式设置的调度信息,
可设置参数:
PTHREAD_INHERIT_SCHED: 新的线程继承创建线程的策略和参数!
PTHREAD_EXPLICIT_SCHED:新的线程继承策略和参数来自于
schedpolicy和schedparam属性中显式
设置的调度信息!
scope,表示线程间竞争资源的范围
POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,
目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
scope有两种取值:
PTHREAD_SCOPE_SYSTEM: 与系统中所有线程一起竞争资源 PTHREAD_SCOPE_PROCESS: 仅与同进程中的线程竞争CPU
示例:
3. 线程属性及属性设置
3.1 线程属性结构体
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈尾警戒缓冲区大小
int stackaddr_set;
void * stackaddr; // 线程栈的位置
size_t stacksize; // 线程栈的大小
}pthread_attr_t;
3.2. 线程属性设置流程
1) 初始化线程属性对象
pthread_attr_init
头文件: #include <pthread.h>
函数原型: int pthread_attr_init(pthread_attr_t *attr);
函数功能: 初始化线程属性对象
函数参数: attr:待初始化的线程属性对象
函数返回值: 成功返回0
失败返回错误码
2) 设置指定的属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
3) 利用属性创建线程
4) 回收线程属性对象
pthread_attr_destroy
头文件: #include <pthread.h>
函数原型: int pthread_attr_destroy(pthread_attr_t *attr);
函数功能: 回收线程属性对象
函数参数: attr:待回收的线程属性对象
函数返回值: 成功返回0
失败返回错误码
#include <unistd.h> // 提供Unix系统调用
#include <pthread.h> // 多线程编程
#include <stdio.h> // 标准输入输出
#include <string.h> // 字符串处理
#include <stdlib.h> // 标准库函数int thread_num = 0; // 全局变量,用于线程计数// 线程执行函数
void* routine(void* argp)
{thread_num--; // 线程结束时减少计数return NULL; // 线程正常退出
}int main(int argc, char** argv)
{// 声明线程属性变量pthread_attr_t attr;// 初始化线程属性变量pthread_attr_init(&attr);// 获取和设置线程栈大小size_t stacksize = 0;// 设置线程栈大小为4MBpthread_attr_setstacksize(&attr, 4 * 1024 * 1024);// 获取实际设置的栈大小pthread_attr_getstacksize(&attr, &stacksize);printf("缺省线程栈空间大小:%lu MB\n", stacksize / 1024 / 1024);// 设置线程为分离状态(detached)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 创建线程,使用自定义属性pthread_t id;pthread_create(&id, &attr, routine, NULL);thread_num++; // 增加线程计数// 销毁线程属性变量(释放资源)pthread_attr_destroy(&attr);/*// 被注释的代码:尝试等待分离线程(会失败)int error = 0;if(error = pthread_join(id, NULL)){printf("join:%s\n", strerror(error));}*/// 忙等待,直到线程计数为0while(thread_num) ;return 0;
}
关键机制详解
1. 线程属性设置
pthread_attr_t attr;
pthread_attr_init(&attr); // 初始化属性对象
2. 栈大小设置
pthread_attr_setstacksize(&attr, 4 * 1024 * 1024); // 设置为4MB
pthread_attr_getstacksize(&attr, &stacksize); // 验证设置
栈大小说明:
默认栈大小通常为2-10MB,取决于系统和编译器
设置更大的栈用于需要大量局部变量的线程
设置更小的栈用于创建大量线程的场景
3. 线程分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
分离状态 vs 可连接状态:
状态 | 特点 | 是否需要pthread_join | 资源回收 |
---|---|---|---|
PTHREAD_CREATE_JOINABLE (默认) | 可等待线程结束 | 需要 | 手动调用pthread_join回收 |
PTHREAD_CREATE_DETACHED | 分离状态 | 不能等待 | 自动回收资源 |
4. 被注释的pthread_join代码
/*
if(error = pthread_join(id, NULL))
{printf("join:%s\n", strerror(error));
}
*/
这里被注释是有原因的:
线程被设置为分离状态后,不能再调用
pthread_join()
如果取消注释,会返回
EINVAL
错误
5. 线程同步机制
while(thread_num) ; // 忙等待,直到thread_num为0
工作原理:
主线程创建子线程时:
thread_num++
(变为1)子线程结束时:
thread_num--
(变为0)主线程在忙等待循环中检测到这个变化后退出
程序执行流程
主线程初始化属性,设置栈大小和分离状态
主线程创建分离线程,增加线程计数
主线程进入忙等待循环
子线程执行,减少线程计数
子线程退出,系统自动回收资源(因为是分离线程)
主线程检测到thread_num为0,退出循环
程序结束
潜在问题和改进
1. 忙等待(Busy Waiting)问题
while(thread_num) ; // CPU占用率高
改进方案:使用条件变量
#include <pthread.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void* routine(void* argp) {pthread_mutex_lock(&mutex);thread_num--;pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);return NULL;
}// 主线程中:
pthread_mutex_lock(&mutex);
while(thread_num) {pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
2. 内存排序问题
thread_num--; // 可能存在可见性问题
改进方案:使用原子操作或互斥锁
#include <stdatomic.h>
atomic_int thread_num = 0;// 或者使用互斥锁保护
3. 完整的改进版本
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int thread_num = 0;void* routine(void* argp) {pthread_mutex_lock(&mutex);thread_num--;printf("子线程结束,剩余线程数: %d\n", thread_num);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_attr_t attr;pthread_attr_init(&attr);// 设置线程属性pthread_attr_setstacksize(&attr, 4 * 1024 * 1024);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);// 创建线程pthread_t id;pthread_mutex_lock(&mutex);pthread_create(&id, &attr, routine, NULL);thread_num++;printf("创建线程,当前线程数: %d\n", thread_num);pthread_mutex_unlock(&mutex);// 等待线程结束pthread_mutex_lock(&mutex);while(thread_num > 0) {pthread_cond_wait(&cond, &mutex);}pthread_mutex_unlock(&mutex);pthread_attr_destroy(&attr);printf("所有线程结束,程序退出\n");return 0;
}
这个程序很好地演示了线程属性的使用,特别是栈大小设置和分离状态的配置。