线程属性
- 本节作为指引性介绍,linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程的调度策略
struct sched schedparam;//线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程栈的设置
void* stackaddr; //线程栈的启始位置
size_t stacksize; //线程栈大小
}pthread_attr_t;
在上面我们可以看到,关于这个结构体中的相关参数
- 线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、栈地址(stack address)、优先级(priority)、分离的状态(detached state)、调度策略和参数(scheduling policy and parameters)。默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
线程的分离属性
线程的分离状态决定一个线程以什么样的方式来终止自己。
- 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
- 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
相关函数
- 初始化线程属性——int pthread_attr_init(pthread_attr_t *attr); 成功:0;失败:错误号
- 销毁线程属性所占用的资源——int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失败:错误号
- 设置线程属性,分离or非分离——int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- 获取程属性,分离or非分离——int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
- 参数:
- attr:已初始化的线程属性
- detachstate: PTHREAD_CREATE_DETACHED(分离线程);PTHREAD _CREATE_JOINABLE(非分离线程)
设置线程分离属性的步骤
- 定义线程属性变量 pthread_attr_t attr
- 初始化attr, pthread_attr_init(&attr)
- 设置线程为分离或非分离 pthread_attr_setdetachstate(&attr,detachstate);
- 创建线程pthread_create(&tid,&attr,thread_fun,NULL); 所有的系统都会支持线程的分离状态属性,
注意:
- 以默认方式启动的线程,在线程结束后不会自动释放占有的系统资源,要在主控线程中调用pthread_join()后才会释放;
- 以分离状态启动的线程,在线程结束后会自动释放所占有的系统资源,这个时候就不需要调用pthread_join方法了;
- 分离属性在网络通讯中使用的比较多;
- 以分离状态创建的线程就不需要去调用pthread_join了,同时以分离状态去创建的线程,是不能够获取线程返回的结果。
/*DATE: 2019-04-23
*AUTHOR CP
*DESCRIPTION: 设置线程分离属性
*如果在创建线程的时候就知道不需要了解线程的终止状态,那么可以修改pthread_attr_t结构体detachstate属性。
*让线程以分离状态启动。可以使用 pthread_attr_ setdetachstate函数来设置线程的分离状态属性。
*线程的分离属性有
*两种合法值
* PTHREAD_CREATE_DETACHED分离的
* PTHREAD_CREATE_J0INABLE非分离的, 可连接的
* int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)
* int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
* 便用 pthread_attr_getdetachstate可以获得线程的分离状态属性
*
* 设置线程分离属性的步骤
* 1.定义线程属性变量 pthread_attr_t attr
* 2.初始化attr, pthread_attr_init(&attr)
* 3.设置线程为分离或非分离 pthread_attr_setdetachstate(&attr,detachstate);
* 所有的系统都会支持线程的分离状态属性,
*/
#include "apue.h"
#include <pthread.h>
void *thread_fun1(void *arg)
{
printf("I'm new thread 1\n");
return (void *)1;
}
void *thread_fun2(void *arg)
{
printf("I'm new thread 2\n");
return (void *)2;
}
int main()
{
pthread_t tid1, tid2;
int err;
pthread_attr_t attr; // 定义属性变量
pthread_attr_init(&attr); // 初始化属性
//pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置属性状态,置为以分离
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); // 设置属性状态,置为可连接
// 创建线程1
err = pthread_create(&tid1, &attr, thread_fun1, NULL);
if (err)
{
printf("create new thread 1 failed\n");
return;
}
// 创建线程1,默认属性
err = pthread_create(&tid2, NULL, thread_fun2, NULL);
if (err)
{
printf("create new thread 2 failed\n");
return;
}
// 连接线程1
err = pthread_join(tid1, NULL);
if (!err)
{
printf("join thread 1 success\n");
}
else
{
printf("join thread 1 failed\n");
}
// 连接线程2
err = pthread_join(tid2, NULL);
if (!err)
{
printf("join thread 2 success\n");
}
else
{
printf("join thread 2 failed\n");
}
// 销毁属性变量
pthread_attr_destroy(&attr);
return 0;
}
运行结果:
分离状态:
连接状态:
- 如上所陈述:turtle采用的是默认的JOIN方式去启动,而rabbit采用的是DETACHED的方式去启动,注意: detached的方式去启动的时候,是不需要通过pthread_join方法去阻塞等待回收的,并且detached的方式启动的,这个时候线程是不会有返回值返回来的。
线程栈属性
- 对于进程来说,虚拟地址空间的大小是固定的,进程中只有一个,因此它的大小通常不是问题。但对线程来说,同样的虚拟地址被所有的线程共享。如果应用程序使用了太多的线程,致使线程累计超过可用的虚拟地址空间,这个时候就需要减少线程默认的栈大小。另外,如果线程分配了大量的自动变里或者线程的栈帧太深,那么这个时候需要的栈要比默认的大。
- 如果用了虚拟地址空间,可以使用malloc或者mmap来为其他分配空间,并修改栈的位置。
相关函数
- 初始化线程属性——int pthread_attr_init(pthread_attr_t *attr); 成功:0;失败:错误号
- 销毁线程属性所占用的资源——int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失败:错误号
- 设置栈位置——int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize); 成功:0;失败:错误号
- 获取栈位置——int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr, size_t *stacksize); 成功:0;失败:错误号
- 参数stackaddr是栈的内存单元最低地址,并不一定是的栈的开始,对于一些处理器,栈的地址是从高往低的,那么stackaddr是的栈结尾位置;
- 参数stacksize是栈的大小;
- 当然也可以单独获取或者修改栈的大小,而不去修改栈的地址对于大小设置,不能小于PTHREAD_STACK_MINl(需要头文件limits.h),终端下输入ulimit -s命令可查看。
对于遵循POSIX标准的操作系统来说,并不一定要支持线程栈属性,因此必须要检查
- 编译阶段:使用_POSIX_THREAD_ATTR_STACKADDR和_POSIX_ATTR_STACKSIZE符号来检查系统是否支持线程栈属性,如果系统定义了这些符号,就说明它支持相应的线程栈属性。
- 运行阶段:把_SC_THREAD_ATTR_STACKADDR和_SC_THREAD_ATTR_STACKSIZE参数传给sysconf函数,检查系统对线程栈属性的支持情况。
线程属性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线程属性被修改了,操作系统可能把它取为页大小的整数倍。如果线程的栈指针溢出到警戒区域,应用程序就可能通过信号接收到出错信息。
/*DATE: 2019-04-23
*AUTHOR CP
*DESCRIPTION: 设置线程栈属性
* 修改栈属性
* 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); // 获取栈位置
*
* 参数stackaddr是栈的内存单元最低地址,并不一定是的栈的开始,对于一些处理器,栈的地址是从高往低的,那么stackaddr是的栈结尾位置;
* 参数stacksize是栈的大小;
* 当然也可以单独获取或者修改栈的大小,而不去修改栈的地址对于大小设置,不能小于PTHREAD_STACK_MIN.
*
* int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); // 设置栈大小
* int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize); // 获取栈大小
* 对于大小的设置,在创建线程之后,还可以修改。
*/
#include "apue.h"
#include <pthread.h>
#include <limits.h>
pthread_attr_t attr;
void *thread_fun(void *arg)
{
size_t stacksize;
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
pthread_attr_getstacksize(&attr, &stacksize);
printf("new thread stack size is %d\n", stacksize);
pthread_attr_setstacksize(&attr, 16398);
pthread_attr_getstacksize(&attr, &stacksize);
printf("new thread stack size is %d\n", stacksize);
#endif
return (void *)1;
}
int main()
{
pthread_t tid;
int err;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
pthread_attr_setstacksize(&attr,PTHREAD_STACK_MIN);
#endif
err = pthread_create(&tid, &attr, thread_fun, NULL);
if (err)
{
printf("create new thread failed\n");
return;
}
pthread_join(tid, NULL);
return 0;
}
运行结果:
让我们上面提到的几种属性总结一下,请见下表:
同步属性
互斥量属性(进程共享属性)
- 进程间也可以使用互斥锁,来达到同步的目的。但应在pthread_mutex_init初始化之前,修改其属性为进程间共享。mutex的属性修改函数主要有以下几个。
主要应用函数
- 用于定义mutex锁的【属性】——pthread_mutexattr_t mattr
- 初始化一个mutex属性对象——int pthread_mutexattr_init(pthread_mutexattr_t *attr);
- 销毁mutex属性对象 (而非销毁锁)——int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
- 获取mutex属性——int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
- 修改mutex属性——int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
-
参2:pshared取值:
线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有)
进程锁:PTHREAD_PROCESS_SHARED
进程共享属性需要检测系统是否支持,可以检测宏_POSIX_THREAD_PROCESS_SHARED.
进程间使用mutex来实现同步:
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/wait.h>
struct mt
{
int num;
pthread_mutex_t mutex;
pthread_mutexattr_t mutexattr;
};
int main(void)
{
int fd, i;
struct mt *mm;
pid_t pid;
fd = open("./mt_test", O_CREAT | O_RDWR, 0777);
ftruncate(fd, sizeof(*mm));
mm = mmap(NULL, sizeof(*mm), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
unlink("./mt_test");
//mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
memset(mm, 0, sizeof(*mm));
pthread_mutexattr_init(&mm->mutexattr); //初始化mutex属性对象
pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED); //修改属性为进程间共享
pthread_mutex_init(&mm->mutex, &mm->mutexattr); //初始化一把mutex琐
pid = fork();
if (pid == 0)
{
for (i = 0; i < 10; i++)
{
pthread_mutex_lock(&mm->mutex);
(mm->num)++;
printf("-child----num++ %d\n", mm->num);
pthread_mutex_unlock(&mm->mutex);
sleep(1);
}
}
else if (pid > 0)
{
for (i = 0; i < 10; i++)
{
sleep(1);
pthread_mutex_lock(&mm->mutex);
mm->num += 2;
printf("-parent---num+=2 %d\n", mm->num);
pthread_mutex_unlock(&mm->mutex);
}
wait(NULL);
}
pthread_mutexattr_destroy(&mm->mutexattr); // 销毁mutex属性对象
pthread_mutex_destroy(&mm->mutex); // 销毁mutex
munmap(mm, sizeof(*mm)); // 释放映射区
return 0;
}
类型属性
获取/设置互斥量的类型属性
- int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
- int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type);
- 两者的返回值都是:若成功则返回0,否则返回错误编号。
实例:
代码:
#include "apue.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
int main()
{
char *shm = "myshm";
char *shm1 = "myshml";
char *buf;
int shmid1, shmid2;
pid_t pid;
pthread_mutex_t *mutex; // 互斥量
pthread_mutexattr_t mutexattr; // 互斥量属性
// 打开共享内存
shmid2 = shm_open(shm1, O_RDWR | O_CREAT, 0644);
// 调整共享内存的大小
ftruncate(shmid2, 100);
// 映射共享内存,MAP_SHARED属性表明,对共享内存的任何修改都会影响到其他进程
mutex = (pthread_mutex_t *)mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, shmid2, 0);
pthread_mutexattr_init(&mutexattr);
#ifdef _POSIX_THREAD_PROCESS_SHARED
pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED);
#endif
pthread_mutex_init(mutex, &mutexattr);
// 打开共享内存
shmid1 = shm_open(shm, O_RDWR | O_CREAT, 0644);
// 调整共享内存的大小
ftruncate(shmid1, 100);
// 映射共享内存,MAP_SHARED属性表明,对共享内存的任何修改都会影响到其他进程
buf = (char *)mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, shmid1, 0);
pid = fork();
if (pid == 0)
{
// 休眠一秒,让父进程先运行
sleep(1);
printf("I'm child process\n");
pthread_mutex_lock(mutex);
// 将共享内存内容,修改为hello
memcpy(buf, "hello", 6);
printf("child buf is: %s\n", buf);
pthread_mutex_unlock(mutex);
}
else if (pid > 0)
{
printf("I'm parent process\n");
pthread_mutex_lock(mutex);
// 修改共享内存内容,修改为world
memcpy(buf, "world", 6);
sleep(3);
printf("parent buf is: %s\n", buf);
pthread_mutex_unlock(mutex);
}
pthread_mutexattr_destroy(&mutexattr);
pthread_mutex_destroy(mutex);
// 解除映射
munmap(buf, 100);
// 消除共享内存
shm_unlink(shm);
munmap(mutex, 100);
// 消除共享内存
shm_unlink(shm1);
return 0;
}
运行结果:
读写锁属性 读写锁仅仅支持进程共享属性
- pthread_rwlockattr_init——对读写锁对象属性进行初始化
- pthread_rwlockattr_destroy——销毁该属性结构
- 两者的返回值都是:若成功则返回0,否则返回错误编号。
进程共享属性
- int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * restrict attr, int *restrict pshared);
- int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
- 上面两个函数和互斥量中对应的两个函数功能是一样的。
条件变量属性 条件变量支持进程进程共享属性和时钟属性。
- pthread_condattr_init——对条件变量属性初始化
- pthread_condattr_destroy——销毁该属性结构
进程共享属性
- int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
- int pthread_condattr_setpshared(pthread_condattr_t *attr,int pshared);
- 上面两个函数和互斥量中对应的两个函数功能是一样的。
时钟属性
- 时钟属性控制计算pthread_cond_timedwait函数的超时参数(tspr)时采用的是哪个时钟。可以使用 pthread_condattr_getclock函数获取可被用于 pthread_cond_timedwait函数的时钟ID,在使用 pthread_cond_timedwait函数前需要用pthread_condattr_t对象对条件变量进行初始化。
- int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id); - 两者的返回值都是:若成功则返回0,否则返回错误编号.