写在前面
多线程编程平时经常用到,最近看了几篇很不错的多线程与多进程的文章,下面是一些简要整理。
线程和进程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序、数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。
随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
线程优先级
优先级调度决定了线程按照什么顺序轮流执行,在具有优先级调度的系统中,线程拥有各自的线程优先级(Thread Priority)。具有高优先级的线程会更早地执行,而低优先级的线程通常要等没有更高优先级的可执行线程时才会被执行。
在优先级调度环境下,线程优先级的改变有三种方式:
一、用户指定优先级;
二、根据进入等待状态的频繁程度提升或降低优先级(由操作系统完成,一般是提升IO密集型线程,即频繁进入等待的线程,对于很少等待的CPU密集型线程,则降低其优先级,避免饿死其他线程);
三、长时间得不到执行而被提升优先级,避免线程饿死。
线程安全与锁
为了解决多个线程访问统一资源可能造成的冲突,我们需要将多个线程对同一数据的访问同步(即一个线程访问数据时,其他线程不得对同一数据进行访问),确保线程安全。常用的有以下几个机制。
二元信号量
二元信号量(Binary Semaphore)是一种最简单的锁,它有两种状态:占用和非占用。它适合只能被唯一一个线程独占访问的资源。当二元信号量处于非占用状态时,第一个试图获取该二元信号量锁的线程会获得该锁,并将二元信号量锁置为占用状态,之后其它试图获取该二元信号量的线程会进入等待状态,直到该锁被释放。
信号量
多元信号量允许多个线程访问同一个资源,多元信号量简称信号量(Semaphore),对于允许多个线程并发访问的资源,这是一个很好的选择。一个初始值为N的信号量允许N个线程并发访问。线程访问资源时首先获取信号量锁,进行如下操作:
一、将信号量的值减1;
二、如果信号量的值小于0,则进入等待状态,否则继续执行;
访问资源结束之后,线程释放信号量锁,进行如下操作:
一、将信号量的值加1;
二、如果信号量的值小于1(等于0),唤醒一个等待中的线程;
互斥量
互斥量(Mutex)和二元信号量类似,资源仅允许一个线程访问。与二元信号量不同的是,互斥量要求哪个线程获取了该互斥量锁就由哪个线程释放,其它线程越俎代庖释放互斥量是无效的,而二元信号量是可以的。
临界区
临界区(Critical Section)是一种比互斥量更加严格的同步手段。临界区的作用范围仅限于本进程,其它的进程无法获取该锁。除此之处,临界区与互斥量的性质相同。
读写锁
读写锁(Read-Write Lock)允许多个线程同时对同一个数据进行读操作,而只允许一个线程进行写操作。对同一个读写锁,有两种获取方式:共享的(Shared)和独占的(Exclusive)。当锁处于自由状态时,试图以任何一种方式获取锁都能成功,并将锁置为对应的状态;如果锁处于共享状态,其它线程以共享方式获取该锁,仍然能成功,此时该锁分配给了多个线程;如果其它线程试图如独占的方式获取处于共享状态的锁,它必须等待所有线程释放该锁;处于独占状态的锁阻止任何线程获取该锁,不论它们以何种方式。
小注
上面只是对资料[1]的一个简要整理,更详细的内容可以转到[1]进行学习。
参考文献
[1] 编程思想之多进程与多线程