C多线程编程---线程的属性

1、前言:

1、一般使用pthread_create函数创建一个线程,使用默认参数,即将该函数的第二个参数设为NULL,对大多数程序来说,使用默认属性就够了,但我们还是有必要来了解一下线程的有关属性。
2、属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy函数来释放资源。属性对象主要包括
是否绑定
是否分离
堆栈地址
堆栈大小
优先级
默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。

2、线程的绑定状态

轻进程(LWP:Light Weight Process)

关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process):轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。

非绑定状态

默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。
绑定状态

绑定状态

顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。

应用

设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。

#include <pthread.h>
pthread_attr_t attr; pthread_t tid; /*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *) my_function, NULL);
pthread_attr_destroy(&attr);

几个注意点:

1、属性对象必须初始化,否则属性不能生效,创建线程时将返回错误
2、属性对象被销毁,并不影响线程的属性
3、pthread_attr_init是初始化一个线程对象的属性,需要用pthread_attr_destroy对其去除初始化
4、int pthread_attr_init(pthread_attr_t *attr);
返回0,表示函数初始化对象成功。失败时返回一个错误代码。

3、线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。

非分离状态

线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。

分离状态

分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。

线程分离状态的函数:

pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
第二个参数可选为:
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)

注意

问题:
这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。
解决手段:
要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

4、线程的优先级

相关函数

线程的优先级存放在结构sched_param中(缺省为0)。用函数
pthread_attr_getschedparam
pthread_attr_setschedparam

进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。

举例

#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;
pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, &param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, &param);
pthread_create(&tid, &attr, (void *)myfunction, myarg);

5、线程的栈地址

1、POSIX.1定义了两个常量_POSIX_THREAD_ATTR_STACKADDR 和_POSIX_THREAD_ATTR_STACKSIZE检测系统是否支持栈属性。
2、也可以给sysconf函数传递_SC_THREAD_ATTR_STACKADDR或 _SC_THREAD_ATTR_STACKSIZE来进行检测。
3、当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstackaddr和pthread_attr_getstackaddr两个函数分别设置和获取线程的栈地址。传给pthread_attr_setstackaddr函数的地址是缓冲区的低地址(不一定是栈的开始地址,栈可能从高地址往低地址增长)。

6、线程栈大小

1、当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用
2、当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。
3、函数pthread_attr_getstacksize和 pthread_attr_setstacksize提供设置。

7、线程的栈保护区大小

1、在线程栈顶留出一段空间,防止栈溢出。
2、当栈指针进入这段保护区时,系统会发出错误,通常是发送信号给线程。
3、该属性默认值是PAGESIZE大小,该属性被设置时,系统会自动将该属性大小补齐为页大小的整数倍。
4、当改变栈地址属性时,栈保护区大小通常清零。

8、线程的调度策略

POSIX标准指定了三种调度策略:先入先出策略 (SCHED_FIFO)、循环策略 (SCHED_RR) 和自定义策略 (SCHED_OTHER)。SCHED_FIFO 是基于队列的调度程序,对于每个优先级都会使用不同的队列。SCHED_RR 与 FIFO 相似,不同的是前者的每个线程都有一个执行时间配额。SCHED_FIFO 和 SCHED_RR 是对 POSIX Realtime 的扩展。SCHED_OTHER 是缺省的调度策略。

1、新线程默认使用 SCHED_OTHER 调度策略。线程一旦开始运行,直到被抢占或者直到线程阻塞或停止为止。
2、SCHED_FIFO
如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM) 的先入先出线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,则会继续处理该线程,直到该线程放弃或阻塞为止。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS)) 的线程或其调用进程没有有效用户 ID 0 的线程,请使用 SCHED_FIFO,SCHED_FIFO 基于 TS 调度类。
3、SCHED_RR
如果调用进程具有有效的用户 ID 0,则争用范围为系统 (PTHREAD_SCOPE_SYSTEM)) 的循环线程属于实时 (RT) 调度类。如果这些线程未被优先级更高的线程抢占,并且这些线程没有放弃或阻塞,则在系统确定的时间段内将一直执行这些线程。对于具有进程争用范围 (PTHREAD_SCOPE_PROCESS) 的线程,请使用 SCHED_RR(基于 TS 调度类)。此外,这些线程的调用进程没有有效的用户 ID 0。

发布了349 篇原创文章 · 获赞 6 · 访问量 9749

猜你喜欢

转载自blog.csdn.net/qq_23929673/article/details/98482734