Linux系统编程【线程属性】(2)
08. 线程属性(了解)
8.1 概述
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;
主要结构体成员:
-
线程分离状态
-
线程栈大小(默认平均分配)
-
线程栈警戒缓冲区大小(位于栈末尾)
-
线程栈最低地址
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。
线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
8.2 线程属性初始化和销毁
#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
失败:错误号
8.3 线程分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。
- 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当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()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
属性综合代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
void *fun(void *argc)
{
for(int i=0;i<5;i++)
{
printf("pthread do working %d...\n",i);
sleep(1);
}
return NULL;
}
//测试分离
int main()
{
pthread_t tid = -1;
int ret = -1;
pthread_attr_t attr;
//1.初始化线程属性
ret = pthread_attr_init(&attr);
if(ret != 0)
{
printf("pthread_attr_init failed...\n");
return 1;
}
printf("线程属性初始化函数ok...\n");
//2.设置线程为分离状态
ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
if(ret != 0)
{
printf("pthread_attr_setdetachstate failed...\n");
return 1;
}
//3.创建一个线程,使用初始化好的属性
ret = pthread_create(&tid,&attr,fun,NULL);
if(ret != 0)
{
printf("pthread_create failed...\n");
return 1;
}
//4.销毁线程属性
ret = pthread_attr_destroy(&attr);
if(ret != 0)
{
printf("pthread_attr_destroy failed...\n");
return 1;
}
//5.测试当前线程是否是分离状态
ret = pthread_join(tid,NULL);
if(ret != 0)
{
printf("当前线程为分离状态...\n");
}
else
{
printf("当前线程为非分离状态...\n");
}
while(1);
return 0;
}
分析对比:
8.4 线程栈地址
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
失败:错误号
8.5 线程栈大小
当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。
#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
失败:错误号
8.6 综合参考程序
#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;
}
8.7 线程使用注意事项
-
主线程退出其他线程不退出,主线程应调用pthread_exit
-
避免僵尸线程
- a) pthread_join
- b) pthread_detach
- c) pthread_create指定分离属性
被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
-
malloc和mmap申请的内存可以被其他线程释放
-
应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit
-
信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制
09. 作业
1) 创建两个子线程, 第一个子线程输出所有的大写字母(A-Z),第二个子线程输出所有小写字母(a-z),每输出一个字母都要睡眠(usleep)。 100ms
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
//打印小写字母
void* fun2(void *arg)
{
char ch =' ';
for(ch = 'a'; ch<='z';ch++)
{
printf("%c",ch);
usleep(10000);//100ms
}
return NULL;
}
//打印大写字母
void* fun1(void *arg)
{
char ch =' ';
for(ch = 'A'; ch<='Z';ch++)
{
printf("%c",ch);
usleep(10000);//100ms
}
return NULL;
}
int main()
{
int ret = -1;
pthread_t tid1, tid2;
//创建两个线程
pthread_create(&tid1,NULL,fun1,NULL);
pthread_create(&tid2,NULL,fun2,NULL);
//等待两个线程结束
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf("\n main thread exit...\n");
return 0;
}
xcc@ubuntu:~/8th$ ./a.out
AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz
main thread exit…
xcc@ubuntu:~/8th$
扩展
1.写一个以当前时间命名的文件
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<time.h>
#define SIZE 32
int main()
{
time_t t = -1;
struct tm *pT = NULL;
char file_name[SIZE];
//获取时间 秒
t= time(NULL);
if(t == -1)
{
perror("time");
return 1;
}
printf("time:%ld\n",t);
//获取当前时间
// printf("ctime:%s",ctime(&t));
// 转化为时间
pT = localtime(&t);
if(NULL == pT)
{
perror("localtime");
return 1;
}
//转化为文件
memset(file_name,0,SIZE);
sprintf(file_name, "%s %d%d%d%d%d%d.log","touch",pT->tm_year+1900,pT->tm_mon+1,pT->tm_mday,pT->tm_hour,pT->tm_min,pT->tm_sec);
printf("file_name:%s\n",file_name);
system(file_name);
return 0;
}
- 点赞
- 收藏
- 关注作者
评论(0)