Linux C/C++编程的线程属性

【图书推荐】《Linux C与C++一线开发实践(第2版)》_linux c与c++一线开发实践pdf-CSDN博客
《Linux C与C++一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

Linux系统与编程技术_夏天又到了的博客-CSDN博客

Linux C/C++编程的线程创建-CSDN博客

POSIX标准规定线程具有多个属性。那么,具体有哪些属性呢?线程主要的属性包括分离状态(Detached State)、调度策略和参数(Scheduling Policy and Parameters)、作用域(Scope)、栈尺寸(Stack Size)、栈地址(Stack Address)、优先级(Priority)等。Linux为线程属性在/usr/include/bits/pthreadtypes.h中定义了一个联合体pthread_attr_t,注意是联合体而不是结构体,代码如下:

复制代码
union pthread_attr_t
{
    char __size[__SIZEOF_PTHREAD_ATTR_T];
    long int __align;
};

从这个定义中可以看出,属性值都是存放在数组__size中的。这样很不方便存取。别急,Linux已经为我们准备了一组专门用于存取属性值的函数,后面具体讲属性的时候会看到。如果要获取线程的属性,首先要用函数pthread_getattr_np来获取属性结构体值,再用相应的函数来具体获得某个属性值。函数pthread_getattr_np声明如下:

int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);

其中,参数thread是线程ID,attr返回线程属性结构体的内容。如果函数执行成功就返回0,否则返回错误码。注意,使用该函数需要定义宏_GNU_SOURCE,而且要在pthread.h前定义,具体如下:

复制代码
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <pthread.h>

当函数pthread_getattr_np获得的属性结构体变量不再需要的时候,应该用函数pthread_attr_destroy进行销毁。

我们前面用pthread_create创建线程的时候,属性结构体指针参数用了NULL,此时创建的线程具有默认属性,即为非分离、大小为1MB的堆栈,与父进程有同样级别的优先级。如果要创建非默认属性的线程,可以在创建线程之前用函数pthread_attr_init来初始化一个线程属性结构体,再调用相应API函数来设置相应的属性,接着把属性结构体的地址作为参数传入pthread_create。函数pthread_attr_init声明如下:

int pthread_attr_init(pthread_attr_t *attr);

其中,参数attr为指向线程属性结构体的指针。如果函数执行成功就返回0,否则返回一个错误码。

需要注意的一点是:使用pthread_attr_init初始化线程属性,之后(传入pthread_create)需要使用pthread_attr_destroy销毁,从而释放相关资源。函数pthread_attr_destroy声明如下:

int pthread_attr_destroy(pthread_attr_t *attr);

其中,参数attr为指向线程属性结构体的指针。如果函数执行成功就返回0,否则返回一个错误码。

除了在创建时指定属性外,我们也可以通过一些API函数来改变已经创建的线程的默认属性,后面讲具体属性的时候再详述。线程属性的设置方法我们基本了解了,那获取线程属性的方法呢?答案是通过函数pthread_getattr_np,该函数可以获取某个正在运行的线程的属性,函数声明如下:

int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr);

其中,参数thread是要获取属性的线程ID,attr用于返回得到的属性。如果函数执行成功就返回0,否则返回一个错误码。

  1. 分离状态

分离状态是线程一个很重要的属性。POSIX线程的分离状态决定一个线程以什么样的方式来终止自己。要注意和前面线程的状态进行区别,前面所说的线程的状态是不同操作系统上的线程都有的状态(线程当前活动状态的说明),而这里所说的分离状态是POSIX标准下的属性所特有的,用于表明该线程以何种方式终止自己。默认的分离状态是可连接(joinable),即创建线程时如果使用默认属性,则分离状态属性就是可连接的。因此,默认属性下创建的线程是可连接线程。

POSIX下的线程要么是分离的,要么是可连接的(非分离状态)。前者用宏PTHREAD_CREATE_DETACHED表示,后者用宏PTHREAD_CREATE_JOINABLEb表示。默认情况下创建的线程是可连接的,一个可连接的线程是可以被其他线程收回资源和杀死(或称取消)的,并且它不会主动释放资源(比如栈空间),必须等待其他线程来回收其资源。因此我们要在主线程中使用pthread_join函数,该函数是一个阻塞函数,当它返回时,所等待的线程的资源也就被释放了。再次强调,如果是可连接的线程,当线程函数自己返回结束时或调用pthread_exit结束时都不会释放线程所占用的堆栈和线程描述符(总计8000多字节),必须调用pthread_join且返回后,这些资源才会被释放。这对于父进程长时间运行的进程来说,结果会是灾难性的。因为父进程不退出并且没有调用pthread_join,所以这些可连接的线程资源就一直没有释放,相当于变成了僵尸线程,僵尸线程越来越多,以后再想创建新线程将变得没有资源可用。

如果不用pthread_join,并且父进程先于可连接的子线程退出,那么会不会泄露资源呢?答案是不会的。如果父进程先于子线程退出,那么它将被init进程所收养,这个时候init进程就是它的父进程,将调用wait系列函数为其回收资源。再说一遍,一个可连接的线程所占用的内存仅当有线程对它执行pthread_join后才会释放,为了避免内存泄漏,可连接的线程在终止时,要么被设为DETACHED(可分离),要么使用pthread_join来回收资源。另外,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程将得到错误代码ESRCH。

了解了可连接线程,我们再来看可分离的线程。这种线程运行结束时,其资源将立刻被系统回收。可以理解为这种线程能独立(分离)出去,可以自生自灭,父线程不用管。将一个线程设置为可分离状态有两种方式。一种是调用函数pthread_detach,将线程转换为可分离线程。另一种是在创建线程时就将它设置为可分离状态,基本过程是首先初始化一个线程属性的结构体变量(通过函数pthread_attr_init),然后将其设置为可分离状态(通过函数pthread_attr_setdetachstate),最后将该结构体变量的地址作为参数传入线程创建函数 pthread_create,这样所创建出来的线程就直接处于可分离状态。

函数pthread_attr_setdetachstate用来设置线程的分离状态属性,声明如下:

int pthread_attr_setdetachstate(pthread_attr_t * attr, int detachstate);

其中,参数attr是要设置的属性结构体;detachstate是要设置的分离状态值,可以取值PTHREAD_CREATE_DETACHED或PTHREAD_CREATE_JOINABLE。如果函数执行成功就返回0,否则返回非零错误码。

【例8.6】创建一个可分离线程

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

复制代码
#include <iostream>
#include <pthread.h>

using namespace std;

void *thfunc(void *arg)
{
	cout<<("sub thread is running\n");
	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t thread_id;
	pthread_attr_t thread_attr;
	struct sched_param thread_param;
	size_t stack_size;
	int res;

	res = pthread_attr_init(&thread_attr);
	if (res)
		cout<<"pthread_attr_init failed:"<<res<<endl;

	res = pthread_attr_setdetachstate( &thread_attr,PTHREAD_CREATE_DETACHED);
	if (res)
		cout<<"pthread_attr_setdetachstate failed:"<<res<<endl;
 
	res = pthread_create(   &thread_id, 	&thread_attr, thfunc,
		NULL);
	if (res )
		cout<<"pthread_create failed:"<<res<<endl;
	cout<<"main thread will exit\n"<<endl;
	
	sleep(1);
	return 0;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

复制代码
# g++ -o test test.cpp -lpthread
# ./test
main thread will exit

sub thread is running
#

在上面的代码中,我们首先初始化了一个线程属性结构体,然后设置其分离状态为PTHREAD_CREATE_DETACHED,并用这个属性结构体作为参数传入线程创建函数中。这样创建出来的线程就是可分离线程,意味着该线程结束时,它所占用的任何资源都可以立刻被系统回收。程序的最后,我们让main线程挂起1秒,让子线程有机会执行。因为如果main线程很早就退出,将会导致整个进程很早退出,子线程就没机会执行了。

有读者可能会想,如果子线程执行的时间很长,那么sleep到底应该休眠多少秒呢?有没有一种机制不用sleep函数,而让子线程完整执行呢?答案是有的,对于可连接线程,主线程可以用pthread_join函数等待子线程结束;而对于可分离线程,并没有这样的函数,但可以采用这样的方法:先让主线程退出而进程不退出,一直等到子线程退出了进程才退出,即在主线程中调用函数pthread_exit,在main线程中如果调用了pthread_exit,那么此时终止的只是main线程,而进程的资源会为由main线程创建的其他线程保持打开的状态,直到其他线程都终止。值得注意的是,如果在非main线程(其他子线程)中调用pthread_exit,就不会有这样的效果,只会退出当前子线程。下面我们重新改写【例8.6】,不用sleep,显得更专业一些。

【例8.7】创建一个可分离线程,且main线程先退出

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

复制代码
#include <iostream>
#include <pthread.h>

using namespace std;

void *thfunc(void *arg)
{
	cout<<("sub thread is running\n");
	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_t thread_id;
	pthread_attr_t thread_attr;
	struct sched_param thread_param;
	size_t stack_size;
	int res;

	res = pthread_attr_init(&thread_attr);  // 初始化线程结构体
	if (res)
		cout<<"pthread_attr_init failed:"<<res<<endl;

	res=pthread_attr_setdetachstate( &thread_attr,PTHREAD_CREATE_DETACHED);
	    // 设置分离状态
	if (res)
		cout<<"pthread_attr_setdetachstate failed:"<<res<<endl;
 
	res = pthread_create(   &thread_id, 	&thread_attr, thfunc, NULL);
          // 创建一个可分离线程
		
	if (res )
		cout<<"pthread_create failed:"<<res<<endl;
	cout<<"main thread will exit\n"<<endl;
	
	pthread_exit(NULL);  // 主线程退出,但进程不会在此刻退出,下面的语句不会再执行
	cout << "main thread has  exited,this line will not run\n" << endl;  
    // 此句不会执行
	return 0;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

复制代码
# g++ -o test test.cpp -lpthread
# ./test
main thread will exit

sub thread is running
#

正如我们所预料的那样,main线程中调用了函数pthread_exit,将退出main线程,但进程并不会在此刻退出,而是要等到子线程结束后才退出。因为是分离线程,所以当它结束时,所占用的资源会立刻被系统回收。如果是一个可连接线程,则必须在创建它的线程中调用 pthread_join来等待可连接线程结束,并释放该线程所占的资源。因此,在上面的代码中,如果我们创建的是可连接线程,则main函数中不能调用pthread_exit预先退出。在此我们再总结一下可连接线程和可分离线程最重要的区别:一个可连接的线程在自己退出时或pthread_exit时都不会释放它所占用的堆栈和线程描述符(总计8000多字节),这些资源需要通过其他线程调用pthread_join之后才会被释放;相反,一个分离的线程是不能被其他线程回收或杀死的,它所占用的资源在终止时由系统自动释放。

除了直接创建可分离线程外,还能把一个可连接线程转换为可分离线程。这有一个好处,就是把线程的分离状态转为可分离后,它自己退出或调用pthread_exit时,就可以由系统回收其资源了。转换方法是调用函数pthread_detach,该函数可以把一个可连接线程转换为一个可分离线程,声明如下:

int pthread_detach(pthread_t thread);

其中,参数thread是要设置为分离状态的线程的ID。如果函数执行成功就返回0,否则返回一个错误码,比如错误码EINVAL表示目标线程不是一个可连接的线程,ESRCH表示该ID的线程没有找到。要注意的是,如果一个线程已经被其他线程连接了,则pthread_detach不会产生作用,并且该线程继续处于可连接状态。同时,如果一个线程已成功地进行了pthread_detach,再想要去连接它,则必定失败。

获取分离状态的函数是pthread_attr_getdetachstate,该函数声明如下:

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

其中,参数attr为属性结构体指针,detachstate返回分离状态。如果函数执行成功就返回0,否则返回错误码。

下面我们来看一个例子,首先创建一个可连接线程,然后获取其分离状态。

【例8.8】获取线程的分离状态属性

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

复制代码
#ifndef _GNU_SOURCE  
#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */  
#endif  
#include <pthread.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <errno.h>  
      
#define handle_error_en(en, msg) \     		// 输出自定义的错误信息
     do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)  
      
static void * thread_start(void *arg)  
{  
	int i,s;  
	pthread_attr_t gattr;  // 定义线程属性结构体
      
	s = pthread_getattr_np(pthread_self(), &gattr);   
	// 获取当前线程属性结构值,该函数前面讲过了
	if (s != 0)  
		handle_error_en(s, "pthread_getattr_np");  // 打印错误信息 
      
	printf("Thread's detachstate attributes:\n");  
 
	s = pthread_attr_getdetachstate(&gattr,&i);// 从属性结构值中获取分离状态属性
	if (s)  
		handle_error_en(s, "pthread_attr_getdetachstate");  
	printf("Detach state        = %s\n",   		// 打印当前分离状态属性
		(i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :  
		(i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :  
		"???");  
	 
	pthread_attr_destroy(&gattr);  
}  
      
int main(int argc, char *argv[])  
{  
	pthread_t thr;  
	int s;  
 
	s = pthread_create(&thr, NULL, &thread_start, NULL);  // 创建线程
	if (s != 0)  
	{
		handle_error_en(s, "pthread_create"); 
	     return 0;
	} 
      
	pthread_join(thr, NULL); 			// 等待子线程结束
	return 0;
}  

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

复制代码
[root@localhost Debug]# ./test
Thread's detachstate attributes:
Detach state = PTHREAD_CREATE_JOINABLE

从运行结果可见,默认创建的线程就是一个可连接线程,即其分离状态属性是可连接的。

下面我们再看一个例子,把一个可连接线程转换为可分离线程,并查看转换前后的分离状态属性。

【例8.9】把可连接线程转换为可分离线程

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

复制代码
#ifndef _GNU_SOURCE  
#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */  
#endif  
#include <pthread.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <errno.h>  
     
static void * thread_start(void *arg)  
{  
	int i,s;  
	pthread_attr_t gattr;  
 
	s = pthread_getattr_np(pthread_self(), &gattr);  
	if (s != 0)  
		printf("pthread_getattr_np failed\n");  
    
	s = pthread_attr_getdetachstate(&gattr, &i);  
	if (s)  
		printf(  "pthread_attr_getdetachstate failed");  
	printf("Detach state        = %s\n",
		(i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :  
		(i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :  
		"???");  

	pthread_detach(pthread_self()); // 转换线程为可分离线程
	
	s = pthread_getattr_np(pthread_self(), &gattr);  
	if (s != 0)  
		printf("pthread_getattr_np failed\n");  
	s = pthread_attr_getdetachstate(&gattr, &i);  
	if (s)  
		printf(" pthread_attr_getdetachstate failed");  
	printf("after pthread_detach,\nDetach state        = %s\n",
		(i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :  
		(i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :  
		"???");  
	
	 pthread_attr_destroy(&gattr);  // 销毁属性
}  
      
int main(int argc, char *argv[])  
{  
	pthread_t thread_id;  
	int s;  
 
	s = pthread_create(&thread_id, NULL, &thread_start, NULL);  
	if (s != 0)  
	{
		printf("pthread_create failed\n"); 
		return 0;
	}
	pthread_exit(NULL);// 主线程退出,但进程并不马上结束
}  

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

复制代码
[root@localhost Debug]# ./test 
Detach state = PTHREAD_CREATE_JOINABLE
after pthread_detach,
Detach state = PTHREAD_CREATE_DETACHED
  1. 栈尺寸

除了分离状态属性外,线程的另一个重要属性是栈尺寸。这对于我们在线程函数中开设栈上的内存空间非常重要。局部变量、函数参数、返回地址等都存放在栈空间里,而动态分配的内存(比如用malloc)或全局变量等都属于堆空间。注意在线程函数中开设局部变量(尤其是数组)不要超过默认栈尺寸大小。获取线程栈尺寸属性的函数是pthread_attr_getstacksize,声明如下:

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

其中,参数attr指向属性结构体;stacksize用于获得栈尺寸(单位是字节),指向size_t类型的变量。如果函数执行成功就返回0,否则返回错误码。

【例8.10】获得线程默认栈尺寸大小和最小尺寸

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

复制代码
#ifndef _GNU_SOURCE  
#define _GNU_SOURCE     /* To get pthread_getattr_np() declaration */  
#endif  
#include <pthread.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <errno.h>  
#include <limits.h>
static void * thread_start(void *arg)  
{  
	int i,res;  
	size_t stack_size;
	pthread_attr_t gattr;  
 
	res = pthread_getattr_np(pthread_self(), &gattr);  
	if (res)  
		printf("pthread_getattr_np failed\n");  
    
	res = pthread_attr_getstacksize(&gattr, &stack_size);
	if (res)
		printf("pthread_getattr_np failed\n"); 
	
	printf("Default stack size is %u byte; minimum is %u byte\n", stack_size, PTHREAD_STACK_MIN);
	 
	
	 pthread_attr_destroy(&gattr);  
}  
      
int main(int argc, char *argv[])  
{  
	pthread_t thread_id;  
	int s;  
 
	s = pthread_create(&thread_id, NULL, &thread_start, NULL);  
	if (s != 0)  
	{
		printf("pthread_create failed\n"); 
		return 0;
	}
	pthread_join(thread_id, NULL); // 等待子线程结束
}  

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

复制代码
[root@localhost Debug]# ./test
Default stack size is 8392704 byte; minimum is 16384 byte
  1. 调度策略

线程的调度策略也是线程的一个重要属性。一个线程肯定有一种策略来调度它。进程中有了多个线程后,就要管理这些线程如何去占用CPU,这就是线程调度。线程调度通常由操作系统来安排,不同操作系统的调度策略(或称调度方法)不同,比如有的操作系统采用轮询法来调度。在理解线程调度之前,先要了解一下实时与非实时。实时就是指操作系统对中断等的响应时效性非常高,非实时正好相反。目前,VxWorks属于实时操作系统,Windows和Linux则属于非实时操作系统,也叫分时操作系统。响应实时的表现主要是抢占,抢占通过优先级来控制,优先级高的任务最先占用CPU。

Linux虽然是一个非实时操作系统,但其线程也有实时和分时之分,具体的调度策略可以分为3种:SCHED_OTHER(分时调度策略)、SCHED_FIFO(先来先服务调度策略)、SCHED_RR(实时调度策略,时间片轮转)。我们创建线程的时候可以指定其调度策略,默认的调度策略是SCHED_OTHER,SCHED_FIFO和SCHED_RR只用于实时线程。

1)SCHED_OTHER

SCHED_OTHER表示分时调度策略(也可称轮转策略),是一种非实时调度策略,系统会为每个线程分配一段运行时间,称为时间片。该调度策略是不支持优先级的,如果我们去获取该调度策略下的最高和最低优先级,可以发现都是0。该调度策略有点像排队上公共厕所,前面的人占用了位置,不出来的话,后面的人是轮不上的,而且不能强行赶出来(不支持优先级,没有VIP特权之说)。

2)SCHED_FIFO

SCHED_FIFO表示先来先服务调度策略,支持优先级抢占。SCHED_FIFO策略下,CPU让一个先来的线程执行完再调度下一个线程,顺序就是按照创建线程的先后。线程一旦占用CPU就会一直运行,直到有更高优先级的任务到达或自己放弃CPU。如果有和正在运行的线程具有同样优先级的线程已经就绪,就必须等待正在运行的线程主动放弃后才可以运行这个就绪的线程。SCHED_FIFO策略下,可设置的优先级范围是1~99。

3)SHCED_RR

SHCED_RR表示时间片轮转(轮循)调度策略,但支持优先级抢占,因此也是一种实时调度策略。SHCED_RR策略下,CPU会分配给每个线程一个特定的时间片,当线程的时间片用完时,系统将重新分配时间片,并将线程置于实时线程就绪队列的尾部,这样就保证了所有具有相同优先级的线程能够被公平地调度。

获取这3种调度策略下可设置的最低和最高优先级,主要使用的函数是sched_get_priority_min和sched_get_priority_max。这两个函数都在sched.h 中声明,具体如下:

int sched_get_priority_min(int policy);

int sched_get_priority_max(int policy);

其中,参数policy为调度策略,可以取值为SCHED_FIFO、SCHED_RR或SCHED_OTHER。函数返回可设置的最低优先级和最高优先级。对于SCHED_OTHER,由于是分时策略,因此返回0;另外两个策略返回的最低优先级是1,最高优先级是99。

【例8.11】获取线程3种调度策略下可设置的最低和最高优先级

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

复制代码
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
main()
{
    printf("Valid priority range for SCHED_OTHER: %d - %d\n",
        sched_get_priority_min(SCHED_OTHER),	// 获取SCHED_OTHER的可设置的最低优先级
         sched_get_priority_max(SCHED_OTHER));	// 获取SCHED_OTHER的可设置的最高优先级
    printf("Valid priority range for SCHED_FIFO: %d - %d\n",
        sched_get_priority_min(SCHED_FIFO),	// 获取SCHED_FIFO的可设置的最低优先级
         sched_get_priority_max(SCHED_FIFO));	// 获取SCHED_FIFO的可设置的最高优先级
    printf("Valid priority range for SCHED_RR: %d - %d\n",
        sched_get_priority_min(SCHED_RR),	// 获取SCHED_RR的可设置的最低优先级
        sched_get_priority_max(SCHED_RR));	// 获取SCHED_RR的可设置的最高优先级
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

复制代码
[root@localhost Debug]# ./test
Valid priority range for SCHED_OTHER: 0 - 0
Valid priority range for SCHED_FIFO: 1 - 99
Valid priority range for SCHED_RR: 1 - 99

对于SCHED_FIFO和SHCED_RR调度策略,由于支持优先级抢占,因此具有高优先级的可运行的(就绪状态下的)线程总是先运行。如果一个正在运行的线程在未完成其时间片时,出现一个更高优先级的线程就绪,那么正在运行的这个线程就可能在未完成其时间片前被抢占,甚至一个线程会在未开始其时间片前就被抢占了,从而要等待下一次被选择运行。当Linux系统切换线程的时候,将执行一个上下文转换的操作,即保存正在运行的线程的相关状态,装载另一个线程的状态,开始新线程的执行。

需要说明的是,虽然Linux支持实时调度策略(比如SCHED_FIFO和SCHED_RR),但它依旧属于非实时操作系统,这是因为实时操作系统对响应时间有着非常严格的要求,而Linux作为一个通用操作系统达不到这一要求(通用操作系统要求能支持一些较差的硬件,从硬件角度就达不到实时要求)。此外,Linux的线程优先级是动态的,也就是说即使高优先级线程还没有完成,低优先级线程还是会得到一定的时间片。

相关推荐
xuanzdhc1 小时前
Linux 基础IO
linux·运维·服务器
愚润求学1 小时前
【Linux】网络基础
linux·运维·网络
bantinghy2 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志3 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手3 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
AWS官方合作商9 天前
AWS ACM 重磅上线:公有 SSL/TLS 证书现可导出,突破 AWS 边界! (突出新功能的重要性和突破性)
服务器·https·ssl·aws
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发
程序员的世界你不懂9 天前
Appium+python自动化(三十)yaml配置数据隔离
运维·appium·自动化
算法练习生9 天前
Linux文件元信息完全指南:权限、链接与时间属性
linux·运维·服务器