关于进程和线程 自我的一些总结
进程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
进程一般由程序,数据集合,进程控制块三部分组成。
程序用于描述进程要完成的功能,是控制进程执行的指令集;
数据集合是程序在执行时所需要的数据和工作区;
程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。
进程具有的特征:
- 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
- 并发性:任何进程都可以同其他进程一起并发执行;
- 独立性:进程是系统进行资源分配和调度的一个独立单位;
- 结构性:进程由程序、数据和进程控制块三部分组成。
线程
线程(Thread),有时被称为轻量级进程(LightweightProcess,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。通常意义上,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间及一些进程级的资源。
内核线程与用户线程
内核线程(Kernel Thread, KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。一般一个处理核心对应一个内核线程。
用户线程就是我们通常意义上所讲的轻量级进程线程。
用户线程与内核线程的对应关系有三种模型:
- 一对一模型
- 多对一模型
- 多对多模型
在现在流行的操作系统中,大都采用多对多的模型。
线程调度
在单处理器对应多线程的情况下,并发是一种模拟出来的状态。操作系统会让这些多线程程序轮流执行,每次仅执行一小段时间(通常是几十到几百个毫秒),这样每个线程就“看起来”在同时执行。这样的一个不断在处理器上切换不同的线程的行为为线程调度。
线程调度中,线程通常有三个状态:
- 运行(Running):此时线程正在执行
- 就绪(Ready):此时线程可以立即运行,但CPU已经被占用。
- 等待(Watching):此时线程正在等待某一事件(通常是I/O)发生,无法执行。
时间片和状态之间的关系
处于运行中的线程拥有一段可以执行的时间,这段时间称为时间片(Time Slice)。
当时间片用尽时,该线程进去就绪状态。
如果在时间片用尽之前线程就开始等待某件事。那么它会进入等待状态。
当一个线程离开运行状态时,调度系统就会选择一个其他的就绪线程继续执行。
在一个处于等待状态的线程所等待的时间发生之后,该线程进入就绪状态。
线程优先级和优先级调度
优先级调度决定了线程按照什么顺序轮流执行,在具有优先级调度的系统中,线程拥有各自的线程优先级(Thread Priority)。具有高优先级的线程会更早地执行,而低优先级的线程通常要等没有更高优先级的可执行线程时才会被执行。
线程的优先级可以由用户手动设置,此外系统也会根据不同情形调整优先级。
在线程调度机制下存在线程饿死现象。一个线程饿死是说它的优先级较低,在它执行之前总是有比它优先级更高的线程等待执行,因此这个低优先级的线程始终得不到执行。为了避免线程饿死,调度系统通常会逐步提升那些等待了很久而得不到执行的线程的优先级。这样,一个线程只要等待了足够长的时间,其优先级总会被提升到可以让它执行的程度,也就是说这种情况下线程终会得到执行,只是时间的问题。
在优先级调度环境下,线程优先级的改变有三种方式:
- 用户指定优先级
- 根据进入等待状态的频繁程度提升或降低优先级(由操作系统完成)
- 长时间得不到执行而被提升优先级。
线程安全,同步,锁
多线程程序处于一个多变的环境当中,可访问的全局变量和堆数据随时都可以被其他的线程改变。因此多线程程序在并发时数据的一致性非常重要。
为了避免多个线程同时读写同一个数据而产生预料不到的后果,我们将各个线程堆同一个数据的访问同步(Synchronization) 。所谓同步(synchronization)就是指一个线程访问数据时,其它线程不得对同一个数据进行访问,即同一时刻只能有一个线程访问该数据,当这一线程访问结束时其它线程才能对这它进行访问。
同步最常见的方式就是使用锁(Lock) ,也称为线程锁。锁是一种非强制机制,每一个线程在访问数据或资源之前,首先试图获取(Acquire)锁,并在访问结束之后释放(Release)锁。在锁被占用时试图获取锁,线程会进入等待状态,直到锁被释放再次变为可用。
二元信号量
二元信号量(Binary Semaphore)是一种最简单的锁,它有两种状态:占用和非占用。它适合只能被唯一一个线程独占访问的资源。当二元信号量处于非占用状态时,第一个试图获取该二元信号量锁的线程会获得该锁,并将二元信号量锁置为占用状态,之后其它试图获取该二元信号量的线程会进入等待状态,直到该锁被释放。
多元信号量
多元信号量允许多个线程访问同一个资源,多元信号量简称信号量(Semaphore),对于允许多个线程并发访问的资源,这是一个很好的选择。一个初始值为N的信号量允许N个线程并发访问。线程访问资源时首先获取信号量锁,进行如下操作:
- 将信号量的值减1;
- 如果信号量的值小于0,则进入等待状态,否则继续执行;
访问资源结束之后,线程释放信号量锁,进行如下操作:
- 将信号量的值加1;
- 如果信号量的值小于1(等于0),唤醒一个等待中的线程;
互斥量
互斥量(Mutex)和二元信号量类似,资源仅允许一个线程访问。与二元信号量不同的是,信号量在整个系统中可以被任意线程获取和释放,也就是说,同一个信号量可以由一个线程获取而由另一线程释放。而互斥量则要求哪个线程获取了该互斥量锁就由哪个线程释放,其它线程无法释放互斥量。
临界区
临界区(Critical Section)是一种比互斥量更加严格的同步手段。互斥量和信号量在系统的任何进程都是可见的,也就是说一个进程创建了一个互斥量或信号量,另一进程试图获取该锁是合法的。而临界区的作用范围仅限于本进程,其它的进程无法获取该锁。除此之处,临界区与互斥量的性质相同。
读写锁
读写锁(Read-Write Lock)允许多个线程同时对同一个数据进行读操作,而只允许一个线程进行写操作。这是因为读操作不会改变数据的内容,是安全的;而写操作会改变数据的内容,是不安全的。对同一个读写锁,有两种获取方式:共享的(Shared)和独占的(Exclusive)。当锁处于自由状态时,试图以任何一种方式获取锁都能成功,并将锁置为对应的状态;如果锁处于共享状态,其它线程以共享方式获取该锁,仍然能成功,此时该锁分配给了多个线程;如果其它线程试图如独占的方式获取处于共享状态的锁,它必须等待所有线程释放该锁;处于独占状态的锁阻止任何线程获取该锁,不论它们以何种方式。获取读写锁的方式总结如下:
读写锁的状态 | 以共享方式获取 | 以独占方式获取 |
---|---|---|
自由 | 成功 | 成功 |
共享 | 成功 | 等待 |
独占 | 等待 | 等待 |
可重入(Reentrant)
一个函数被重入,表示这个函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。一个函数要被重入,只有两种情况:
- 多个线程同时执行这个函数
- 函数自身(可能是经过多层调用之后)调用自身。
一个函数被称为可重入的,表明该函数被重入之后不会产生任何不良后果。举个例子,如下这个sqr函数是可重入的
int sqr(int x)
{
return x*x;
}
一个函数要称为可重入的,必须包含这几个特点:
- 不适用任何(局部)静态过全局的飞const变量
- 不反悔任何(局部)静态或全局的非const变量的指针
- 仅依赖于调用方法的参数。
- 不依赖任何单个资源的锁(mutex)等
- 不调用任何不可重入的函数。
可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用。
使用多线程的优点
- 多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;
- 当前没有进行处理的任务时可以将处理器时间让给其它任务;
- 占用大量处理时间的任务可以定期将处理器时间让给其它任务;
- 可以随时停止任务;
- 可以分别设置各个任务的优先级以优化性能。
进程与线程的区别
- 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
- 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
- 调度和切换:线程上下文切换比进程上下文切换要快得多。
- 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。而线程在执行过程中,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
以上总结参考了以下内容:
- 《程序员的自我修养》
- 编程思想之多线程与多进程(1)
- 编程思想之多线程与多进程(2)
- 进程和线程关系及区别
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。