Download presentation
Presentation is loading. Please wait.
Published byLiani Budiaman Modified 5年之前
1
3.5 线程 3.5.1 问题的提出 进程的引入使操作系统得以完成对并发执行的多道程序动态特征的描述和资源共享的管理,因而进程既是调度的基本单位又是资源分配的基本单位。进程所具有的这两个特点构成了程序并发执行的基础,但同时又导致进程切换过程中由于进程映像过大而带来的时空开销。因此,如果系统中创建的进程过多,或进程切换的频率过高,则会使系统效率下降,限制了并发度的进一步提高。 进程切换所带来的庞大开销是因为进程与资源分配有关,操作系统如何降低进程切换所需要的时空开销呢? 那么,操作系统是否也可以将调度的基本单位改成一种只用于调度执行而不涉及资源分配的单位?这个单位就是“线程”。 ——减小进程上下文切换开销及更好地支持SMP(对称多处理) 2019/4/26 操作系统与实验 第3章第7讲
2
线程是进程内相对独立的、可独立调度和指派的执行单元。 线程不是资源分配的基本单位; 线程从属于某个进程
3.5.2 线程及其特征 线程是进程内相对独立的、可独立调度和指派的执行单元。 线程不是资源分配的基本单位; 线程从属于某个进程 将进程称为重型进程,线程称为轻型进程。 2019/4/26 操作系统与实验 第3章第7讲
3
3.5.2 线程及其特征 1.线程具有以下特征: 线程是进程中的一个相对独立的可执行单元。
线程是操作系统中的基本调度单位,在线程中包含调度所需要的基本信息。 在具备线程机制的操作系统中,一个进程中至少包含一个线程,以线程作为调度单位。 线程自己并不拥有资源,它与同一进程中的其它线程共享该进程所拥有的资源。由于线程之间涉及资源共享,所以需要同步机制来实现进程内多线程之间的通信。 与进程类似,线程还可以创建其它线程,线程也有生命周期,也有状态的变化。 2019/4/26 操作系统与实验 第3章第7讲
4
2.线程与进程的主要区别与联系 调度与资源分配
在引入线程的操作系统中,线程是调度和指派的基本单位,而进程是资源的拥有单位。在同一进程中,线程的切换不会引起进程的切换;而在不同的进程中进行线程的切换,比如从一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换。 线程不拥有资源,但可以访问它所在的进程的资源,从而获得系统资源。 并发性 引入线程的操作系统具有更好的并发性。 系统开销 进程切换时有很大的时空开销,而线程切换时只需要保存和设置少量的寄存器内容,时空开销很小。另外,由于同一进程内的多个线程共享进程的同一地址空间,因此,多个线程之间的同步与通信也非常容易实现,甚至不需要操作系统内核的干预。 在一个进程内使用多线程的优点主要在于创建线程的工作量要比创建进程的工作量小,不涉及资源分配,不同线程之间的切换比进程之间的切换要快。 2019/4/26 操作系统与实验 第3章第7讲
5
3.4.3 线程的状态与线程控制块 与进程类似,线程也是一个动态的概念,也有一个从创建到消亡的过程,它也具有以下几个基本状态:
就绪态:具备执行条件,等待调度。 运行态:正占有CPU 处于运行中。 阻塞态:等待某个事件发生。 结束态:线程结束执行,释放其寄存器上下文和堆栈的内容。 线程使用线程控制块TCB(Thread Control Block)来描述。线程由与其相关的堆栈、寄存器和线程控制块TCB组成。寄存器可以用来存储线程内的局部变量,但是不能存储其它线程的局部变量。 线程的控制原语包括线程创建、线程终止、线程阻塞等等。 2019/4/26 操作系统与实验 第3章第7讲
6
3.5.4 线程的分类 1.用户级线程 用户级线程ULT(User-Level Thread)由用户程序创建,并由用户程序对其进行调度和管理。操作系统内核并不知道有用户级线程的存在,因而用户级线程与内核无关。我们也称之为“纯ULT方法”。MS-DOS、Unix属于这种方法。 其优点在于: 应用程序中的线程转换开销比内核级的开销要小得多。 线程的调度算法与操作系统的调度算法无关。 用户级线程方法可以适用于任何操作系统,因为它与内核无关。 其缺点在于: 当进程中某线程执行一个系统调用而被阻塞时,会导致本进程的其它线程也阻塞。 在纯ULT方式下,多线程不便利用多处理器,因为每次只有一个进程的一个线程在一个CPU上运行。 2019/4/26 操作系统与实验 第3章第7讲
7
3.5.4 线程的分类 2.内核级线程 内核级线程KLT(Kernel-Level Thread)中,所有线程的创建、调度、管理都由操作系统内核负责。一个用户进程可以按多线程方式编写程序,当它被提交给多线程操作系统运行时,内核为它创建一个进程和一个线程,线程在运行中还可以创建新的线程。我们称之为“纯KLT方法”。Windows NT属于此类。 其优点在于: 内核可以调度一个进程中的多个线程,使其同时在多个处理器上并发运行,从而提高运行的速度和效率。 当进程中的一个线程被阻塞时,该进程中的其它线程仍可运行。 内核本身也可以用线程的方式实现。 其缺点在于: 同一进程中的线程切换要有两次态的转换(用户态→内核态→用户态),因为线程的调度运行在内核态,而应用程序运行在用户态。 2019/4/26 操作系统与实验 第3章第7讲
8
3.5.4 线程的分类 3.多线程操作系统 将上述用户级与内核级结合的操作系统称为多线程操作系统。这种方式下,一方面内核支持多线程的创建、调度、管理等操作,另一方面系统为用户提供一个线程库,允许用户程序建立、调度和管理其创建的线程。 2019/4/26 操作系统与实验 第3章第7讲
9
3.5.5 线程与进程的关系 1:1 Unix System V n:1 OS/2、MVS、MACH 1:n Ra n:n TRIX
线程:进程 描述 操作系统举例 1:1 一个进程对应一个线程 Unix System V n:1 每个进程动态拥有地址空间和资源,一个进程可以产生多个线程并发执行 OS/2、MVS、MACH 1:n 一个线程可以在多个进程间转移 Ra n:n 包含n:1和1:n的性质 TRIX 2019/4/26 操作系统与实验 第3章第7讲
10
其中对于线程 : 进程为n : 1的情况,多线程与进程的关系如下图:
2019/4/26 操作系统与实验 第3章第7讲
11
3.5.5 线程与进程的关系 总的来说,任何两个单独的进程都拥有各自独立的地址空间,尽管有时候它们的地址空间中的信息在内容上是相同的;与此不同的是,同一进程中的两个线程将共享相同的地址空间。由于多个线程在同一地址空间运行,所以,一旦某线程对其虚拟空间做任何改变,其它线程就会马上感知到。 2019/4/26 操作系统与实验 第3章第7讲
12
*补充* 在传统的Unix模型中,当一个进程需要由另一个实体执行某件事时,该进程派生(fork)一个子进程,让子进程去进行处理。
在Linux中线程编程符合Posix.1标准,称为Pthreads。所有的pthread函数都以pthread_开头。 POSIX (Portable Operating System Interface) 表示可移植操作系统接口。除UNIX以外,很多操作系统都支持POSIX标准,如DEC OpenVMS 和 Microsoft Windows NT等。 2019/4/26 操作系统与实验 第3章第7讲
13
3.5.6 线程的优缺点 1.线程的系统开销小 使用线程所具有的优点主要表现在以下几个方面:
创建一个进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式;而运行于一个进程中的多个线程,它们皆共享使用相同的地址空间,共享大部分数据,但拥有各自的栈空间,拥有独立的执行序列。启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。 这个优点常用于以下方面: 提高应用程序的响应时间。 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序也有利于理解和修改。 2019/4/26 操作系统与实验 第3章第7讲
14
3.5.6 线程的优缺点 2.多线程间的通信比进程间通信方便
对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过系统提供的通信机制进行(参阅第4章),这种方式不仅费时,而且很不方便。而线程则不然,由于同一进程中的诸线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。 使用线程所具有的缺点主要表现在以下方面: 1 .由于共享资源而不利于资源的管理和保护 (进程正相反) 2019/4/26 操作系统与实验 第3章第7讲
15
3.5.8 Linux线程的特点 Linux采用创建进程的方法来创建线程,是一种类似于进程的线程,称之为“新进程”,这种线程使用系统调用clone创建。 使用clone系统调用创建线程与使用fork系统调用创建进程的不同之处在于: 线程与创建它的进程共享相同的地址空间,拥有各自的栈,形成两个不同的线程(父子线程); 子进程与其父进程拥有各自不同地址空间,形成两个不同的进程(父子进程)。 clone系统调用创建的线程与使用fork系统调用创建的进程的相同之处在于:无论线程还是子进程,在创建后它们所具有的内部标识皆为进程标识符。 Linux中的线程表面上看是进程,因为它具有进程标识,但是它本质上却不是进程,因为它没有属于自己的进程映像,而是与创建它的进程共享,因而它不占用额外的系统资源,在这一点上它具有线程的本质特征。 所以,我们可以将Linux中的线程理解为:使用创建进程的方式创建的线程。 2019/4/26 操作系统与实验 第3章第7讲
16
3.5.9 线程举例 1.Linux线程系统调用 ⑴.创建线程 格式:
int pthread_create(pthread_t*thread, pthread_attr_t*attr, void* (*start_routine)(void *), void*arg) 参数说明: thread——返回创建的线程标识符ID,是个指向线程标识符的指针; attr——设置线程的属性,如果使用NULL则表示为系统默认属性; start_routine——线程执行函数的地址,该函数必须具有void*返回值; arg——线程执行函数的参数,如果不带参数则可以使用NULL 。 返回值:成功返回0 出错则返回错误码 注:①.该系统调用调用clone函数来创建线程。 ②.由于线程没有自己的存储空间,所以,它所执行的函数需要在创建它的进程中加以定义。 2019/4/26 操作系统与实验 第3章第7讲
17
1.Linux线程系统调用 ⑵.线程终止 格式: int pthread_exit(void*retval) 参数说明:
功能:终止一个线程。 返回值:成功返回0 出错则返回错误码 2019/4/26 操作系统与实验 第3章第7讲
18
1.Linux线程系统调用 ⑶.等待线程终止 格式:
int pthread_join (pthread_t th, void **thread_return) 参数说明: th——被等待的线程标识符, *thread_return——用户定义的指针,它可以用来存储被等待线程的终止信息。类似于wait( [int*stat_addr,] 0 )中的*stat_addr。如果不需要取终止信息则可使用NULL。 功能:回收终止的线程,类似于回收进程waitpid()。调用它的函数将一直等待到被等待的线程结束为止。当函数返回时,被等待线程的资源被收回。 返回值:成功返回0 出错则返回错误码 所用头文件: #include <pthread.h> 2019/4/26 操作系统与实验 第3章第7讲
19
2.Linux线程举例 [例3-12]在主函数中创建一个线程,其功能是循环输出3次“This is the second pthread.”;主线程也循环输出3次“This is the main pthread.”,观察并分析它们的执行过程。 源程序清单,文件名:pthread.c #include <stdio.h> #include <pthread.h> //线程所用头函数 /*定义线程的执行函数*/ void thread(void) //必须使用void作为返回类型 { int i; for(i=0;i<3;i++) printf(“This is the second pthread.\n”); //显示自己是子线程 sleep(1); } 2019/4/26 操作系统与实验 第3章第7讲
20
/*创建一个子线程并指定执行函数,函数不带参数*/
main() { /*定义线程内部标识 */ pthread_t threadid; int i,ret; /*创建一个子线程并指定执行函数,函数不带参数*/ ret=pthread_create(&threadid,NULL,(void*)thread,NULL); if(ret!=0) printf ("Create pthread error!\n"); exit (1); } /*主线程循环输出3次*/ for(i=0;i<3;i++) { printf("This is the main pthread.\n"); sleep(1); } pthread_join(threadid,NULL); //等待子线程结束 exit(0); } 2019/4/26 操作系统与实验 第3章第7讲
21
带有线程的源程序在进行编译时需要使用参数 -lpthread: gcc -lpthread –o 目标文件名 源文件名 编译和运行该程序后的结果为:
由上述输出结果可以看出,当线程作为调度的基本单位时,创建的子线程连同主线程共形成两个线程被操作系统调度,因而产生以上类似于进程随机调度的运行结果。 2019/4/26 操作系统与实验 第3章第7讲
22
3.多线程举例 [例3-13]定义一个多线程标识符数组,然后创建多个线程,每个线程执行的程序都是显示该线程标识符及其下标值。主线程则等待每个线程终止,显示结束提示符后结束运行。设创建5个子线程。 程序清单:文件名multhread.c #include<pthread.h> #include<stdio.h> #define MAXTHREAD 5 //定义多线程标识符数组元素个数 void*threaf_fuction(void*arg) //定义线程执行函数 { printf(arg); printf("\n"); } 2019/4/26 操作系统与实验 第3章第7讲
23
pthread_t threads[MAXTHREAD]; //定义多线程标识符数组 /*定义线程执行函数的参数*/
/*主线程*/ main() { int tid; pthread_t threads[MAXTHREAD]; //定义多线程标识符数组 /*定义线程执行函数的参数*/ char message[][10]={"thread1","thread2","thread3","thread4","thread5"}; /*循环创建5个子线程*/ int i; for(i=0;i<5;i++) /*创建子线程并指定执行函数及参数*/ tid=pthread_create(&threads[i],NULL,threaf_fuction,message[i]); if(tid!=0) perror("Thread creation failed"); exit(1); } 2019/4/26 操作系统与实验 第3章第7讲
24
pthread_join(threads[i],NULL); //等待5个子线程终止
/*主线程*/ for(i=0;i<5;i++) pthread_join(threads[i],NULL); //等待5个子线程终止 printf("All thread finished.\n"); //输出结束提示 exit(0); } 2019/4/26 操作系统与实验 第3章第7讲
25
作业 习题3 22 2019/4/26 操作系统与实验 第3章第7讲
Similar presentations