linux线程属性

线程属性

  • 本节作为指引性介绍,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(非分离线程)

设置线程分离属性的步骤

  1. 定义线程属性变量 pthread_attr_t attr
  2. 初始化attr, pthread_attr_init(&attr)
  3. 设置线程为分离或非分离 pthread_attr_setdetachstate(&attr,detachstate);
  4. 创建线程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,否则返回错误编号.

猜你喜欢

转载自blog.csdn.net/qq_22847457/article/details/89461222