你也会注意到任务管理器中有CPU使用率的信息。这是因为进程也有一个使用计算机处理器的执行顺序。这个执行顺序就是线程。这个线程由CPU上正在使用的寄存器,线程使用的堆栈以及保存线程当前状态的存储器共同定义。存储器和堆栈的概念对那些经常处理底层内存分配的同僚们来说应该很熟悉;然而,对.NET Framework 中的堆栈来说,你可以把它看成一块用来快速访问数据,存储值类型或者指向对象、方法参数以及每个方法调用的本地数据的内存区域。
单线程进程
正如上面提到的,每个进程至少有一个执行顺序(或线程)。创建一个进程包括让进程开始运行指令。初始线程又称作原始线程或主线程。线程的实际执行顺序由你的程序中的代码决定。例如,在一个简单的.NET Windows 窗体应用程序中,主线程在你的工程中的静态Main()方法处启动。它开始于对Application.Run()的调用。
现在我们大概知道了什么是一个进程以及它至少有一个线程,让我们从图2中的虚拟模型角度看看这种关系。
图2
让我们看一下上面的图,你会发现线程和数据在同一个隔离区间内。这是要说明你在进程中定义的数据可以被线程访问。线程在处理器上执行同时按需要使用进程中的数据。这看起来很简单;我们有一个物理隔离的进程,所以其他进程不可能修改这个数据。在这个进程看来,它是系统中正在运行的唯一进程。我们不需要知道其他进程的细节以及它们的关联线程就可以让我们自己的进程工作。
说得更准确一些,线程实际上是指向一个进程指令流的一个指针。线程本身并不包含指令,它只是通过由数据和分支决策确定的指令指出了当前以及未来可能的路径。
时间片
当我们讨论多任务时,我们指出操作系统为每个程序分配一定时间,然后中断当前运行程序并允许另外一个程序执行。这并不完全准确。处理器实际上为进程分配时间。进程可以执行的时间被称作“时间片”或者“限量”。时间片的间隔对程序员和任何非操作系统内核的程序来说都是变化莫测的。程序员不应该在他们的程序中将时间片的值假定为一个常量。每个操作系统和每个处理器都可能设定一个不同的时间。
不过,我们之前还没有提到一个涉及并发的潜在问题,而且我们应该考虑如果每个进程都是物理隔离的,那么并发将如何发挥作用。这意味着挑战才刚刚开始,这也是本书的余下部分要重点关注的。我们提到过一个进程在运行时至少有一个线程。我们的进程在任意一个时间点都可能有多于一个任务。例如,它可能需要通过网络访问一个SQL Server 数据库,同时需要绘出用户接口。
多线程的进程
你可能已经知道,我们可以将分配给进程的时间片拆开来用。这是通过在进程内额外生成新线程来实现。你可能想生成一个额外线程来做一些后台的工作,比如访问一个网络或者查询一个数据库。因为这些子线程通常被创建来做一些工作,所以它们通常被称作工作线程。这些线程共享分配给进程的与系统中其他进程隔离开的内存空间。在一个进程内生成新的线程通常被称作自由线程。
自由线程的概念相对于单元线程模型有很大的优势,后者用在Visual Basic 6.0 中。在单元线程中,每个进程都被分配一份它需要的全局数据的拷贝来执行。每个生成的线程都在它自己的进程中生成,以便于线程之间不能共享进程的内存中的数据。我们通过对比来看一下这些模型的差异。图3描述了单元线程概念,而图4描述了自由线程概念。我们不需要在这里花费太多时间,但是理解这些差异对我们很重要:
图3
图4
正如你看到的,每次当你想做一些后台的工作时,它一般在它自己的进程中进行。因此这又称作在进程内运行。这个模型与图4中的线程模型非常不同。
我们得到了使用额外线程的好处以及共享同样数据的能力。要注意一个重要的问题:在同一个时间点只能有一个线程在处理器上执行。进程中的每个线程会在被分配的执行空间内来进行工作。让我们再看一遍图5中的图表来帮助我们理解它是如何工作的。
图5
为了便于理解,本书中使用的例子和图表都假设系统中只有一个处理器。然而,如果计算机中有超过一个处理器的话,你的应用程序使用多线程会高效一些。因为操作系统有两个位置(处理器)来执行线程的代码了。还是用我们之前提过的银行的例子,这类似于我们让一个新出纳开一个新窗口。操作系统负责决定哪个线程运行在哪个处理器上。如果程序员选择,.NET 平台提供控制一个进程使用哪个CPU的功能。这通过System.Diagnostics.Process.ProcessorAffinity属性实现。需要注意的是,这个属性是设置在进程级别的以至于在这个进程中的所有进程都会执行在同样的处理器上。
调度这些线程要比上一张图标中描述的复杂很多,但是就我们的目的来说这已经足够了。由于每个线程都按顺序执行,我们可能被银行工作人员提醒要在银行柜台前排队。我们也要记住这些线程都会在一个很短的时间内被中断。同时,另外一个线程,可能是同一进程里的,也可能是其他进程里的,会开始执行。在我们继续探讨之前,先看看任务管理器。
运行任务管理器并选中进程选项卡。打开了以后,选择查看->选择列面板。你将会看到任务管理器中的列。我们在这里只关心一个列-线程数。选中了以后,你将看到类似以下的内容:
单击确认以后你将看到很多进程有不止一个线程。这证实了你的程序在一个进程中可能有多个线程的想法。