线程详细剖析(三)
摘自《C++多核编程》
6.2 线程和进程的比较
线程和进程都能够提供并发程序执行。当您决定使用多个进程还是多个线程时,上下文切换需要使用的系统资源、吞吐量、实体间通信、程序简化等都是需要考虑的问题。
6.2.1 上下文切换
当您创建一个进程时,可能只需要主线程这一个线程就可以实现进程的功能了。当进程有着多个并发子任务时,多个线程能够在上下文切换的开销较小的情况下提供子任务的异步执行。如果处理器可用性较低或者只有一个内核,并发执行的进程由于需要进行上下文切换而带来较大的开销。相同的情况下,如果使用线程,只有当下一个要指派到处理器的线程来自另一个进程时,才会发生进程上下文切换。较少的开销意味使用的系统资源较少,而且上下文切换的时间也更短。当然,如果有足够的处理器用于周转,那么上下文切换就不再是一个问题。
6.2.2 吞吐量
使用多个线程可以增加应用程序的吞吐量。当只有一个线程时,I/O 请求会让整个进程暂停。有了多个线程后,当一个线程等待I/O请求时,应用程序将继续执行。当一个线程被阻塞时,另一个线程便可以执行。整个应用程序不需要等待每个I/O请求被满足,其他不依赖于被阻塞线程的任务可以继续执行。
6.2.3 实体间的通信
线程与被称为对等线程的进程中其他线程之间,不要去特殊的通信机制。线程可以直接与其他对等线程进行数据的传递和接收。这节省了使用多个进程时,为了建立和维护特殊的通信机制所使用的系统资源。线程是通过使用进程地址空间中的共享内存来通信的。例如,如果进程声明了一个全局队列,进程中的线程A可以保存对线程B要处理的文件名。线程B可以从队列中读取该文件名并处理数据。
进程也可以通过共享内存进行通信,但是进程有着独立的地址空间,因此共享内存存在于进行通信的两个进程的地址空间外部。如果有一个进程希望将它处理的文件名传递给其他进程,可以使用消息队列。需要在涉及的进程的地址空间外部建立这个消息队列,而且通常需要大量的设置才能够很好的工作。这增加了用于维护和访问共享内存所使用的空间和时间。
6.2.4 破坏进程数据
线程可以很轻易的破坏进程的数据。如果没有同步,线程对相同数据片的写入访问可以导致数据竞争,对于进程则不会这样。每个进程有它自己的数据,而且除非设置特殊通信,否则其他进程不能够访问到它们。进程的隔离的地址空间包含数据不受其他进程无意的破坏。线程共享相同地址空间的事实使得若不使用同步,则数据便会面临被破坏的风险。例如:假定一个进程包含三个线程,threadA, threadB, threadC. threadA 和 threadB 更新某个计数器,threadC 读取每个被更新的值,并将这个值用于计算中。threadA 和 threadB 都试图并发的写入到内存的某个位置中,在threadC读取之前, threadB重写了 threadA写入的数据。应当使用同步来确定在 threadC 读取数据之前,计数器不会被更新。
6.2.5 删除整个进程
如果线程产生严重的访问违规,则可能导致整个进程的终止。访问违规不局限于线程,因为它发生在进程的地址空间。线程导致的错误往往比进程导致的错误代价更大。线程可以产生影响所有线程的整个内存空间的数据错误。线程不是隔离的,而进程时隔离的。进程可以发送导致进程终止的访问违规,但是如果违规后果不是很严重,则所有其他进程继续执行,数据错误可以限制在一个进程内。进程可以保护资源不被其他进程任意的访问。线程与进程中所有其他线程共享资源。损害资源的线程会影响整个进程或程序。
6.2.6 被其他程序重用
线程依赖进程,不能够从它们所属的进程中分离开。进程比线程要独立的多。应用程序可以在多个进程之间分配任务,这些进程可以被封装为可以在其他应用程序中使用的模块。线程不能够在创建它们的进程外部生存,因此是不可重用的。
6.2.7 线程与进程的管教类似和差别
线程和进程有很多相似之处,也存在巨大的差别。线程和进程都有id、寄存器组、状态和优先级,而且都支持某种调度策略。与进程类似,线程也有一个环境来向操作系统描述实体,即进程上下文或线程上下文。上下文用来重新构建被抢占的进程或线程。尽管进程所需要的信息远多于线程所需要的信息,但它们的目的是相同的。
线程和子进程不需要额外的初始化或准备就能够共享父进程的资源。进程打开的资源对线程或子进程是立即可访问的。与内核实体类似,线程和子进程会竞争对处理器的使用。父进程对子进程或线程有一定的控制。父进程对子进程或线程进行如下操作:
1)取消
2)挂起
3)重新开始
4)改变优先级
线程或进程可以改变自身的属性和创建新的资源,但是它不能够访问属于其他进程的资源。
如同我们已经指出的那样,线程和进程之间最大的差别在于每个进程都有自己的进程空间,而线程则包含在所属进程的地址空间内。这就是为何线程可以非常容易的共享资源,而且线程间通信如此简单的原因。子进程有自己的地址空间以及它的父进程的数据段的副本,所以当子进程改动它的数据时,不会影响父进程的数据。如果父进程同子进程期望共享数据,则需要创建一块共享内存区域。共享内存是进程间通信机制的一种类型,其中包含了管道以及先进先出调度策略。进程间通信机制用于在进程之间传递数据。
进程可以对它的子进程进行控制,对等线程却是处于相同的级别,无论时谁创建了它们。任意线程只要有权使用另一个对等线程的id,就能够对该线程进行取消、挂起、重新开始或改变优先级的操作。实际上,进程汇总的任意线程都可以通过取消主线程来删除进程,从而终止进程中的所有线程。对主线程的任何更改可能会影响进程中的所有线程。如果主线程的优先级发生了变化,则进程中继承该优先级的所有线程也都会发生变化。
表6-2汇总了线程和进程的关键类似和差别。