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

发布于:2024-12-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

【图书推荐】《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

2. 栈尺寸

除了分离状态属性外,线程的另一个重要属性是栈尺寸。这对于我们在线程函数中开设栈上的内存空间非常重要。局部变量、函数参数、返回地址等都存放在栈空间里,而动态分配的内存(比如用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

3. 调度策略

线程的调度策略也是线程的一个重要属性。一个线程肯定有一种策略来调度它。进程中有了多个线程后,就要管理这些线程如何去占用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的线程优先级是动态的,也就是说即使高优先级线程还没有完成,低优先级线程还是会得到一定的时间片。