Skip to content

LV015-线程属性

本文主要是线程——线程属性的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正 😃。

前边学习线程创建函数的时候我们知道,在创建一个线程的时候我们是可以设置线程的相关属性的,那具体怎么操作呢?在 Linux 下,使用 pthread_attr_t 数据类型定义线程的所有属性,由于多数情况还是默认方式创建,所以这里只做简单的介绍,算是为后边线程分离属性的设置打一个基础吧。

一、线程属性对象的创建与销毁

1. pthread_attr_init()

1.1 函数说明

在 linux 下可以使用 man pthread_attr_init 命令查看该函数的帮助手册。

c
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);

函数说明】初始化线程的属性。

函数参数

  • attr : pthread_attr_t * 类型,指向需要进行初始化的线程属性对象。说实话,我没找到这个结构体的定义,下边的在网上搜到的,应该是都差不多,后边找到的话再补充把。
c
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;

返回值】 int 类型,成功返回 0 ,失败将返回错误码。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
pthread_attr_init(&attr);

注意事项】 Linux 为 pthread_attr_t 对象的每种属性提供了设置属性的接口以及获取属性的接口,所以我们可以不用关心 pthread_attr_t 结构体内部成员的具体情况。

1.2 使用实例

在后边的实例中会有说明。

2. pthread_attr_destroy()

2.1 函数说明

在 linux 下可以使用 man pthread_attr_destroy 命令查看该函数的帮助手册。

c
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_attr_destroy(pthread_attr_t *attr);

函数说明】该函数销毁一个线程属性对象。

函数参数

  • attr : pthread_attr_t * 类型,指向已经进行过初始化的线程属性对象。

返回值】 int 类型,成功返回 0 ,失败将返回错误码。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
pthread_attr_destroy(&attr);

注意事项】当不再需要一个线程属性对象时,应该使用 pthread_attr_destroy() 函数销毁它,销毁线程属性对象对使用该对象创建的线程没有影响。

2.2 使用实例

后边的实例中会有说明。

二、线程栈的属性

每个线程都有自己的栈空间, pthread_attr_t 数据结构中定义了栈的起始地址以及栈大小,调用函数 pthread_attr_getstack() 可以获取这些信息,函数 pthread_attr_setstack() 对栈起始地址和栈大小进行设置。

1. pthread_attr_getstack()

1.1 函数说明

在 linux 下可以使用 man pthread_attr_getstack 命令查看该函数的帮助手册。

c
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);

函数说明】该函数获取线程创建栈属性 stackaddr 和 stacksize 。

函数参数

  • attr : const pthread_attr_t * 类型,参数 attr 指向线程属性对象。

  • stackaddr : void ** 类型,是一个二级指针类型,获取的栈的起始地址信息保存在 *stackaddr 中。

  • stacksize : size_t * 类型,获取的栈大小信息保存在参数 stacksize 所指向的内存中。

返回值】 int 类型,成功返回 0 ,失败将返回错误码。

使用格式】 none

注意事项】 stackaddr 和 stacksize 描述的堆栈内的所有页面都应该是线程可读写的。

1.2 使用实例

后边的实例会有说明。

2. pthread_attr_setstack()

2.1 函数说明

在 linux 下可以使用 man pthread_attr_setstack 命令查看该函数的帮助手册。

c
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);

函数说明】该函数设置线程创建栈属性 stackaddr 和 stacksize 。

函数参数

  • attr : const pthread_attr_t * 类型,参数 attr 指向线程属性对象。

  • stackaddr : void * 类型,设置栈起始地址为指定值。

  • stacksize : size_t * 类型,:设置栈大小为指定值。

返回值】 int 类型,成功返回 0 ,失败将返回错误码。

使用格式】 none

注意事项】 stackaddr 和 stacksize 描述的堆栈内的所有页面都应该是线程可读写的。

2.2 使用实例

后边的实例会有说明。

三、线程分离属性

1. pthread_attr_setdetachstate()

1.1 函数说明

在 linux 下可以使用 man pthread_attr_setdetachstate 命令查看该函数的帮助手册。

c
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

函数说明】该函数设置 detachstate 线程属性(线程的分离状态属性)。

函数参数

  • attr : const pthread_attr_t * 类型,参数 attr 指向线程属性对象。
  • detachstate : int 类型, attr 中线程分离状态属性设置为 detachstate 的值。detachstate 取值情况如下:
PTHREAD_CREATE_DETACHED 新建线程一开始运行便处于分离状态,以分离状态启动线程,无法被其它线程调用 pthread_join()回收,线程结束后由操作系统收回其所占用的资源
PTHREAD_CREATE_JOINABLE 这是 detachstate 线程属性的默认值,正常启动线程,可以被其它线程获取终止状态信息
【**返回值**】 int 类型,成功返回 0 ,失败将返回错误码。

使用格式

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

注意事项】如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create 函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用 pthread_create 的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施。(暂时未遇到,后边遇到了再补充)

1.2 使用实例

c
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_self pthread_join pthread_detach*/
#include <unistd.h> /* sleep */
#include <string.h> /* strerror */

void *threadDetach(void *arg);

int main(int argc, char *argv[])
{
	int ret;
	pthread_t tid;

	/* 0. 设置线程分离属性 */
	pthread_attr_t attr;
	pthread_attr_init(&attr);/* 对attr对象进行初始化 */
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);/* 设置以分离状态启动线程 */


	/* 1. 创建一个线程 */
	ret = pthread_create(&tid, &attr, threadDetach, NULL);
	printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);

	/* 2. 主线程分离子线程或者在子线程中分离自己 */
	sleep(1);
	/* 3. 分离后进行回收,若分离成功,则会出错 */
	ret = pthread_join(tid, NULL);
	if(ret != 0)
		printf("pthread_join error: %s\n", strerror(ret));
	sleep(1);
	return 0;
}

void *threadDetach(void *arg)
{
	printf("This is a thread test! pid=%d,tid=%lu\n", getpid(), pthread_self());

	sleep(1);
	pthread_exit("thread return!");
}

在终端执行以下命令编译程序:

shell
gcc test.c -Wall -l pthread # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

shell
This is main thread,ret=0,tid=139921936299584
This is a thread test! pid=8340,tid=139921936299584
pthread_join error: Invalid argument

2. pthread_attr_getdetachstate()

2.1 函数说明

在 linux 下可以使用 man pthread_attr_getdetachstate 命令查看该函数的帮助手册。

c
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

函数说明】该函数获取线程创建线程的线程分离状态属性。

函数参数

  • attr : const pthread_attr_t * 类型,参数 attr 指向线程属性对象。

  • detachstate : int * 类型,获取 attr 中线程分离状态属性,并保存在 detachstate 的所指向的内存空间中。

返回值】 int 类型,成功返回 0 ,失败将返回错误码。

使用格式】 none

注意事项】 none

2.2 使用实例

这个没咋用过,就了解一下吧。

四、线程优先级属性

1. 线程优先级

在 linux 系统中,我们创建的线程时是可以为其指定优先级和调度策略的。在 linux 系统中的线程分为实时线程和非实时线程,一般创建的线程默认为非实时线程。所有优先级值在 0-99 范围内的,都是实时进程,所以这个优先级范围也可以叫做实时进程优先级,而 100-139 范围内的是非实时进程。

实时线程分 99 个静态优先级,数字越大,优先级越高。在实时线程当中支持抢占调度策略跟轮询调度策略,拥有抢占所有实时线程运行资源的能力,必须拥有超级用户权限 才能够运行。

对于非实时线程,单位时间中,并没有在乎响应能力的一个线程,里面 只有一个静态优先级 0,也就是在非实时线程中,它是没有静态优先级的概念的,它的所有的执行过程都是由系统自动分配的。非实时线程 只有一个静态优先级,所以同时非实时线程的任务无法抢占他人的资源,在非实时线程当中只支持其他调度策略 (自动适配的,系统分配的调度策略)不拥有抢占所有运行资源的能力。支持动态优先级系统自适应,从-20 到 19 的动态优先级 (nice 值)。

2. 调度策略

在linux系统中,线程有哪些调度策略?我们其实可以看一下 man 7 sched,或者看这里sched(7) - Linux manual page,这里简单了解下:

  • 抢占式调度策略

在同一静态优先级的情况下,抢占调度策略的线程一旦运行,便会一直抢占 CPU 资源,而其他同一优先级的只能一直等到这个抢占式调度策略的线程退出才能被运行到(这个时候的非实时线程会有一小部分资源分配到)。也就是说这种调度策略的线程是先到先服务,一旦占用 cpu 则一直运行,一直运行直到有更高优先级任务到达或自己放弃。

  • 轮询式调度策略

在同一静态优先级的情况下,所有线程合理瓜分 时间片,不会一直抢占 CPU 资源(这个时候的非实时线程会有一小部分资源分配到)。这个其实就是时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的 RR 任务的调度公平

在网上查了一下,时间片长度为 100ms,其实一般操作系统的时间片应该是10ms。这个倒是有待查证,感觉不会这么长,后边确认了再更新。

Tips:时间片

内核在微观上,把 CPU 的运行时间分成许多分,然后安排给各个进程轮流运行,造成宏观上所有的进程 仿佛 同时在执行,但实际上对于单核 CPU 来说,同一时刻只会有一个进程运行,线程也是一样。对于双核 CPU,实际上最多只能有两个进程在同时运行,另外我们在 top、vmstat 命令里看到的正在运行的进程,并不是真的在占有着 CPU。

  • 其他普通式调度策略(分时调度策略)

只能作用于非实时线程,由系统自动分配时间片,并目根据运行状态自动分配动态优先级。

注意抢占式调度策略和轮询式调度策略只能在实时线程中被设置,也就是静态优先级 1-99 的区域内设置,普通非实时线程不能设置。

3. 调度策略相关函数

3.1 pthread_attr_setschedpolicy()

3.1.1 函数说明

在 linux 下可以使用 man pthread_attr_setschedpolicy 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

函数说明】该函数用于设置线程的调度策略到线程属性变量中(创建线程时使用)。

函数参数

  • attr: pthread_attr_t 类型指针变量,表示线程属性结构体地址。
  • policy:int 类型变量,表示调度策略,一共有两种,非实时调度策略(SCHED_OTHER)和实时调度策略(SCHED_FIFO, SCHED_RR,其中 SCHED_FIFO 表示抢占式调度,SCHED_RR 是轮询式调度)。

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_RR);

注意事项

(1) 一般来说,系统创建线程时,默认的线程都是 SCHED_OTHER 策略。

(2)为了让 pthread_attr_setschedpolicy()在调用 pthread_create(创建线程)时生效,调用者必须使用 pthread_attr_setinheritsched()将属性对象 attr 的继承调度器属性设置为 PTHREAD_EXPLICIT_SCHED。

3.1.2 使用实例

后边会有一个综合测试的实例。

3.2 pthread_attr_getschedpolicy()

3.2.1 函数说明

在 linux 下可以使用 man pthread_attr_getschedpolicy 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy);

函数说明】该函数用于获取线程的调度策略到线程属性变量中(创建线程时使用)。

函数参数

  • attr: pthread_attr_t 类型指针变量,表示线程属性结构体地址。
  • policy:int 类型指针变量,表示调度策略,一共有两种,非实时调度策略(SCHED_OTHER)和实时调度策略(SCHED_FIFO, SCHED_RR,其中 SCHED_FIFO 表示抢占式调度,SCHED_RR 是轮询式调度)。

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
int policy;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, &policy);

注意事项】 一般来说,系统创建线程时,默认的线程都是 SCHED_OTHER 策略。

3.2.2 使用实例

后边会有一个综合测试的实例。

3.3 pthread_attr_setinheritsched()

3.3.1 函数说明

在 linux 下可以使用 man pthread_attr_setinheritsched 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);

函数说明】该函数用于设置线程的调度策略继承属性到线程属性变量中(创建线程时使用)。一般创建的线程是默认继承父进程的调度策略的,我们再设置线程的优先级是不会生效的,要想使用我们自己设置的调度策略,必须使用此函数设置不继承父进程的调度策略。

函数参数

  • attr: pthread_attr_t 类型指针变量,表示线程属性结构体地址。
  • inheritsched:int 类型变量,表示线程的继承性,一共有两种,PTHREAD_INHERIT_SCHED:使用 attr 创建的线程从创建的线程继承调度属性,忽略 attr 中的调度属性;PTHREAD_EXPLICIT_SCHED:使用 attr 创建的线程从 attributes 对象指定的值中获取调度属性。在新初始化的线程属性对象中,继承调度器属性的默认设置是 PTHREAD_INHERIT_SCHED。

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

注意事项

(1) 一般来说,在新初始化的线程属性对象中,继承调度器属性的默认设置是 PTHREAD_INHERIT_SCHED。

(2)为了让 pthread_attr_setschedpolicy()在调用 pthread_create(创建线程)时生效,调用者必须使用 pthread_attr_setinheritsched()将属性对象 attr 的继承调度器属性设置为 PTHREAD_EXPLICIT_SCHED。

3.3.2 使用实例

后边会有一个综合测试的实例。

3.4 pthread_attr_getinheritsched()

3.4.1 函数说明

在 linux 下可以使用 man pthread_attr_getinheritsched 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched);

函数说明】该函数用于获取线程的调度策略继承属性到线程属性变量中(创建线程时使用)。

函数参数

  • attr: pthread_attr_t 类型指针变量,表示线程属性结构体地址。
  • inheritsched:int 类型指针变量,表示线程的继承性,一共有两种,PTHREAD_INHERIT_SCHED:使用 attr 创建的线程从创建的线程继承调度属性,忽略 attr 中的调度属性;PTHREAD_EXPLICIT_SCHED:使用 attr 创建的线程从 attributes 对象指定的值中获取调度属性。在新初始化的线程属性对象中,继承调度器属性的默认设置是 PTHREAD_INHERIT_SCHED。

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
int inheritsched;
pthread_attr_init(&attr);
pthread_attr_getinheritsched(&attr, &inheritsched);

注意事项】 一般来说,在新初始化的线程属性对象中,继承调度器属性的默认设置是 PTHREAD_INHERIT_SCHED。

3.4.2 使用实例

后边会有一个综合测试的实例。

3.5 pthread_setschedparam()

3.5.1 函数说明

在 linux 下可以使用 man pthread_setschedparam 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param);

函数说明】该函数用于设置线程的调度策略和线程的优先级,前边是直接在初始化的时候,通过线程属性设置,这个函数可以在 线程创建后,在线程内部设置线程的调度策略,它不受 pthread_attr_setinheritsched()函数的影响,也就是说,即便初始化时没有设置不继承父进程调度策略,这里也会将线程配置为我们新设置的调度策略。

函数参数

  • thread : pthread_t 类型变量,表示线程 ID,我们可以在线程内部使用 pthread_self()函数获取线程 ID。
  • policy :int 类型变量,表示调度策略,一共有两种,非实时调度策略(SCHED_OTHER)和实时调度策略(SCHED_FIFO, SCHED_RR,其中 SCHED_FIFO 表示抢占式调度,SCHED_RR 是轮询式调度)。
  • param :struct sched_param 类型结构体指针变量,表示线程的优先级,它其实只有一个成员,就是 sched_priority。
c
struct sched_param {
	int sched_priority;     /* Scheduling priority */
};

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
int policy = SCHED_RR;
struct sched_param param;
param.sched_priority = 90;
pthread_setschedparam(pthread_self(), policy, &param);

注意事项】 如果 pthread_setschedparam()失败,线程的调度策略和参数不会改变。

3.5.2 使用实例

后边会有一个综合测试的实例。

3.6 pthread_getschedparam()

3.6.1 函数说明

在 linux 下可以使用 man pthread_getschedparam 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param);

函数说明】该函数用于获取线程的调度策略继承属性和线程的优先级,在 线程内部 使用。

函数参数

  • thread : pthread_t 类型变量,表示线程 ID,我们可以在线程内部使用 pthread_self()函数获取线程 ID。
  • policy :int 类型指针变量,表示调度策略,一共有两种,非实时调度策略(SCHED_OTHER)和实时调度策略(SCHED_FIFO, SCHED_RR,其中 SCHED_FIFO 表示抢占式调度,SCHED_RR 是轮询式调度)。
  • param :struct sched_param 类型结构体指针变量,表示线程的优先级,它其实只有一个成员,就是 sched_priority。

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
int policy;
struct sched_param param;
pthread_getschedparam(pthread_self(), &policy, &param);

注意事项】 none

3.6.2 使用实例

后边会有一个综合测试的实例。

3.7 pthread_attr_setschedparam()

3.7.1 函数说明

在 linux 下可以使用 man pthread_attr_setschedparam 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);

函数说明】该函数用于设置的优先级到线程属性中(线程 创建时 使用)。

函数参数

  • attr: pthread_attr_t 类型指针变量,表示线程属性结构体地址。
  • param :struct sched_param 类型结构体指针变量,表示线程的优先级,它其实只有一个成员,就是 sched_priority。
c
struct sched_param {
	int sched_priority;     /* Scheduling priority */
};

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
pthread_attr_init(&attr);

struct sched_param param;
param.sched_priority = 90;
pthread_attr_setschedparam(&attr, &param);

注意事项】 none

3.7.2 使用实例

后边会有一个综合测试的实例。

3.8 pthread_attr_getschedparam()

3.8.1 函数说明

在 linux 下可以使用 man pthread_attr_setschedparam 命令查看该函数的帮助手册。

c
// Compile and link with -pthread.

/* 需包含的头文件 */
#include <pthread.h>

/* 函数声明 */
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);

函数说明】该函数用于获取线程优先级到线程属性中(线程 创建时 使用)。

函数参数

  • attr: pthread_attr_t 类型指针变量,表示线程属性结构体地址。
  • param :struct sched_param 类型结构体指针变量,表示线程的优先级,它其实只有一个成员,就是 sched_priority。
c
struct sched_param {
	int sched_priority;     /* Scheduling priority */
};

返回值】 int 类型,成功返回 0,失败返回一个错误值。

使用格式

c
/* 需要包含的头文件 */
#include <pthread.h>

/* 至少应该有的语句 */
pthread_attr_t attr;
pthread_attr_init(&attr);

struct sched_param param;
pthread_attr_getschedparam(&attr, &param);

注意事项】 none

3.8.2 使用实例

后边会有一个综合测试的实例。

4. 优先级设置相关函数

4.1 sched_get_priority_max()

4.1.1 函数说明

在 linux 下可以使用 man sched_get_priority_max 命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sched.h>

/* 函数声明 */
int sched_get_priority_max(int policy);

函数说明】该函数用于获取可用于策略识别的调度算法的最大优先级值。

函数参数

  • policy:int 类型变量,要获取最大优先级值的调度策略。支持的策略值有: SCHED_FIFO、SCHED_RR、SCHED_OTHER、SCHED_BATCH、SCHED_IDLE 和 SCHED_DEADLINE。

返回值】 int 类型,成功返回对应调度策略的最大优先级值,失败返回-1,并设置错误号。

使用格式】none

注意事项】none

4.1.2 使用实例

后边会有一个综合测试的实例。

4.2 sched_get_priority_min()

4.2.1 函数说明

在 linux 下可以使用 man sched_get_priority_min 命令查看该函数的帮助手册。

c
/* 需包含的头文件 */
#include <sched.h>

/* 函数声明 */
int sched_get_priority_min(int policy);

函数说明】该函数用于获取可用于策略识别的调度算法的最小优先级值。

函数参数

  • policy:int 类型变量,要获取最小优先级值的调度策略。支持的策略值有: SCHED_FIFO、SCHED_RR、SCHED_OTHER、SCHED_BATCH、SCHED_IDLE 和 SCHED_DEADLINE。

返回值】 int 类型,成功返回对应调度策略的最小优先级值,失败返回-1,并设置错误号。

使用格式】none

注意事项】none

4.2.2 使用实例

后边会有一个综合测试的实例。

4.3 pthread_setschedparam()

见上一小节的 3.5。

4.4 pthread_getschedparam()

见上一小节的 3.6。

4.5 pthread_attr_setschedparam()

见上一小节的 3.7。

4.6 pthread_attr_getschedparam()

见上一小节的 3.8。

5. 优先级实例

5.1 修改 linux 的 CPU 核心数

优先级的效果需要在在单个核上才能看出,在这里为了方便测试,我直接将虚拟机的 linux 改掉,改为 1 核的,当然也可以用后面 03-操作系统/13-Linux 系统/LV050-CPU 核.md 的那些函数将相关的线程绑定到同一个核上运行。

image-20230305140100387

  • 查看修改后的 linux 核情况
shell
hk@vm:~/6temp/test$ cat /proc/cpuinfo| grep "processor"| wc -l
1
hk@vm:~/6temp/test$ cat /proc/cpuinfo | grep "cpu cores" | uniq
cpu cores       : 1
hk@vm:~/6temp/test$ cat /proc/cpuinfo | grep "physical id" | uniq
physical id     : 0

5.2 示例源码

c
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <sched.h>

void *start_routine(void *arg)
{
	int i, j;
 
	while(1)
	{
		fprintf(stderr, "%c ", *(char *)arg);
		for(i=0; i<100000; i++)
			for(j=0; j<1000; j++);
	}
 
	pthread_exit(NULL);
}
 
 
int main(int argc, char *argv[])
{
 
	pthread_t tid1, tid2, tid3;
	pthread_attr_t attr1, attr2;
	struct sched_param param1, param2;
 
	/* 线程属性变量的初始化 */
	pthread_attr_init(&attr1);
	pthread_attr_init(&attr2);
 
 
	/* 设置线程是否继承创建者的调度策略 PTHREAD_EXPLICIT_SCHED:不继承才能设置线程的调度策略*/
	errno = pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);
	if(errno != 0)
	{
		perror("setinherit failed\n");
		return -1;
	}
 
	/* 设置线程是否继承创建者的调度策略 PTHREAD_EXPLICIT_SCHED:不继承才能设置线程的调度策略*/
	errno = pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);
	if(errno != 0)
	{
		perror("setinherit failed\n");
		return -1;
	}
 
	/* 设置线程的调度策略:SCHED_FIFO:抢占性调度; SCHED_RR:轮寻式调度;SCHED_OTHER:非实时线程调度策略*/
	errno = pthread_attr_setschedpolicy(&attr1, SCHED_RR);
	if(errno != 0)
	{
		perror("setpolicy failed\n");
		return -1;
	}
 
	errno = pthread_attr_setschedpolicy(&attr2, SCHED_RR);
	if(errno != 0)
	{
		perror("setpolicy failed\n");
		return -1;
	}
 
	//设置优先级的级别
	param1.sched_priority = 1;
	param2.sched_priority = 1;
	
	//查看抢占性调度策略的最小跟最大静态优先级的值是多少
	printf("min=%d, max=%d\n", sched_get_priority_min(SCHED_FIFO), sched_get_priority_max(SCHED_FIFO));
 
 
	/* 设置线程静态优先级 */
	errno = pthread_attr_setschedparam(&attr1, &param1);
	if(errno != 0)
	{
		perror("setparam failed\n");
		return -1;
	}
 
	errno = pthread_attr_setschedparam(&attr2, &param2);
	if(errno != 0)
	{
		perror("setparam failed\n");
		return -1;
	}
 
/* 创建三个测试线程 */
    /* 线程 1,优先级 1 */
	errno = pthread_create(&tid1, &attr1, start_routine, (void *)"1");
	if(errno != 0)
	{
		perror("create thread 1 failed\n");
		return -1;
	}
    /* 线程 2,优先级 1 */
	errno = pthread_create(&tid2, &attr2, start_routine, (void *)"2");
	if(errno != 0)
	{
		perror("create thread 2 failed\n");
		return -1;
	}
    /* 线程 3,非实时线程,静态优先级 0 */
	errno = pthread_create(&tid3, NULL, start_routine, (void *)"3");
	if(errno != 0)
	{
		perror("create thread 3 failed\n");
		return -1;
	}
 
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	pthread_join(tid3, NULL);
 
	pthread_attr_destroy(&attr1);
	pthread_attr_destroy(&attr2);
 
	return 0;
}

我们编译并执行程序:

shell
hk@vm:~/6temp/test$ gcc 02_cpu.c -Wall -lpthread
hk@vm:~/6temp/test$ ./a.out 
min=1, max=99
create thread 1 failed
: Operation not permitted

可以看到我们没有权限修改优先级,必须要有管理员权限才可以。

shell
hk@vm:~/6temp/test$ sudo ./a.out
  • 设置优先级的两个线程均为 SCHED_FIFO 时
shell
hk@vm:~/6temp/test$ gcc 02_cpu.c -Wall -lpthread
hk@vm:~/6temp/test$ sudo ./a.out 
min=1, max=99
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ^C

会发现线程 2 根本就没有执行过,这是因为它俩优先级一样,但是线程 1 先被创建,先运行,一旦运行就会一直抢占资源,线程 2 优先级又和线程 1 一样,这样就导致线程 2 根本没有执行过。同时会发现,即便线程 3 是非实时线程,也会得到执行的机会,虽然很少。

  • 设置优先级的两个线程均为 SCHED_RR 时
shell
hk@vm:~/6temp/test$ gcc 02_cpu.c -Wall -lpthread
hk@vm:~/6temp/test$ sudo ./a.out 
min=1, max=99
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 2 2 2 1 1 2 2 1 1 1 2 2 1 1 2 2 2 1 1 2 2 3 1 1 1 2 2 1 1 2 2 2 1 1 2 2 1 1 1 2 2 1 1 2 3 2 2 1 1 2 2 1 1 1 2 2 1 1 2 2 1 1 2 2 2 1 1 3 1 2 2 1 1 2 2 1 1 2 2 2 1 1 1 2 2 1 1 2 2 1 3 1 2 2 2 1 1 1 2 2 1 1 2 2 1 1 2 2 2 1 1 2 2 1 1 1 2 2 1 1 2 2 2 1 1 2 2 1 1 1 2 2 1 1 2 3 2 2 1 1 2 2 1 1 1 2 2 1 1 2 2 2 1 1 2 2 1 1 ^C

会发现,当线程 2 开始执行后,基本就是线程 1 和 2 执行次数相当,线程 3 也会执行,但是也会很少,这就是线程的优先级问题。