线程有两种观点:操作系统和编程语言。在这两种情况下,线程具有的属性都有一些差异。
线程的最小定义是,事物是顺序发生的,一件又一件地发生。
在典型的机器执行模型中,每个线程都有自己的一组通用寄存器和自己的程序计数器。如果机器将特定的寄存器设置为堆栈指针,则每个线程有一个副本。
从操作系统的角度来看,操作系统支持线程所要做的最少工作就是提供一种在线程之间进行切换的方法。这既可以自动发生(强制性多任务处理,也可以仅在线程发出显式请求时才发生(协作性多任务处理;在这种情况下,线程有时被称为纤维)。也存在具有抢占和协作收益的混合模型,例如不同组的线程之间的抢占或任务,但在相同组/任务的线程之间有明确的收益。在线程之间进行切换至少要保存旧线程的寄存器值并恢复新线程的寄存器值。
在提供任务之间隔离的多任务操作系统中(或进程,您可以将这些术语视为OS上下文中的同义词),每个任务都有自己的资源,特别是地址空间,但也有打开的文件,特权等。由操作系统内核提供,它是进程之上的实体。通常,每个任务都有至少一个线程-不执行代码的任务用处不大。操作系统可能会或可能不会在同一任务中支持多个线程。例如原始的Unix没有。任务可以通过安排在多个线程之间切换来运行多个线程-这不需要任何特殊特权。这称为“ 用户线程””,尤其是在Unix环境中。如今,大多数Unix系统确实提供内核线程,特别是因为它是使同一进程的多个线程在不同处理器上运行的唯一方法。
除计算时间外,大多数操作系统资源都附加到任务而不是线程上。某些操作系统(例如Linux)明确定义了堆栈的界限,在这种情况下,每个线程都有自己的线程。但是在某些操作系统中,内核对堆栈一无所知,就它们而言,它们只是堆的一部分。内核通常还为每个线程管理内核上下文,内核上下文是一种数据结构,其中包含有关线程当前正在执行的操作的信息。这使内核可以同时处理系统调用中阻塞的多个线程。
就操作系统而言,任务的线程运行相同的代码,但是在该代码中的不同位置(不同的程序计数器值)。程序代码的某些部分始终在特定线程中执行可能会或可能不会发生,但是通常可以从任何线程调用通用代码(例如,实用程序函数)。所有线程都看到相同的数据,否则它们将被视为不同的任务;如果某些数据只能由特定线程访问,则通常仅是编程语言的权限,而不是操作系统的权限。
在大多数编程语言中,存储在同一程序的线程之间共享。这是并行编程的共享内存模型。它非常流行,但也很容易出错,因为当竞争条件发生时,多个线程可以访问相同的数据时,程序员需要谨慎。请注意,甚至局部变量也可以在线程之间共享:“局部变量”(通常)是指名称仅在一个函数执行期间有效的变量,而另一个线程可以获取指向该变量的指针并对其进行访问。
还有一些编程语言,其中每个线程都有其自己的存储,它们之间的通信是通过在通信通道上发送消息来进行的。这是并发编程的消息传递模型。Erlang是专注于消息传递的主要编程语言;它的执行环境对线程的处理非常轻量级,并且它鼓励使用许多寿命短的线程编写的程序,而与大多数其他编程语言相反,在大多数其他编程语言中,创建线程是一项相对昂贵的操作,而运行时环境不能支持非常大的线程同时的线程数。Erlang的顺序子集(发生在线程中的语言部分,特别是数据操作)(主要)纯粹是功能性的;因此,一个线程可以将消息发送到包含某些数据的另一个线程,并且两个线程都不需要担心该线程在使用该数据时会修改该数据。
某些语言通过提供线程本地存储(有或没有类型系统来区分线程本地存储位置与全局代码存储区)来混合这两种模型。线程本地存储通常是一项便利功能,它允许变量名称在不同线程中指定不同的存储位置。
一些(困难的)后续操作可能对了解什么是线程感兴趣:
- 内核支持多个线程所需要做的最少工作是什么?
- 在多处理器环境中,将线程从一个处理器迁移到另一个处理器需要什么?
- 在没有操作系统支持且不使用内置支持的情况下,以自己喜欢的编程语言实现协作多线程(协程)会怎样?(请注意,大多数编程语言都缺少在单个线程内实现协程的必要原语。)
- 如果编程语言具有并发性但没有线程的(显式)概念,它将是什么样?(主要示例:pi演算。)