第 26 章 线程基础

第 26 章 线程基础

本章内容:

26.2 线程开销

每个线程都有以下要素

  • 线程内核对象(thread kernel object)

  • 线程环境块(thread environment block,TEB)
    TEB 是在用户模式中分配和初始化的内存块。TEB 耗用 1 个内存页。 TEB 包含线程的异常处理链首(head)。线程进入的每个 try 块都在链首插入一个节点(node);线程退出 try 块时从链中删除该节点。

  • 用户模式栈(user-mode stack)
    用户模式栈存储传给方法的局部变量和实参。Windows 默认为每个线程的用户模式栈分配 1 MB 内存。

  • 内核模式栈(kernel-mode stack)
    应用程序代码向操作系统中的内核模式函数传递实参时,还会使用内核模式栈。在 32 位 Windows 上运行,内核模式栈大小是 12 KB; 64 位 Windows 是 24 KB。

  • DLL 线程连接(attach)和线程分离(detach)通知

上下文切换

Windows 任何时刻只将一个线程分配给一个 CPU。那个线程能运行一个“时间片”,时间片到期,Windows 就上下文切换到另一个线程。每次上下文切换都要求 Windows 执行以下操作:

  1. CPU 寄存器的值保存到当前正在运行的线程的内核对象内部的一个上下文结构中。

  2. 从现有线程集合中选出一个线程供调度。如果该线程由另一个进程拥有,还必须切换虚拟地址空间。

  3. 将所选上下文结构中的值加载到 CPU 的寄存器中。

安装了多个 CPU(或者一个多核 CPU)的计算机可以真正同时运行几个线程。Windows 为每个 CPU 内核都分配一个线程,每个内核都自己执行到其他线程的上下文切换。

线程的主要几点性能影响:

  • 线程的创建、销毁都是很昂贵的;
  • 线程上下文切换有极大的性能开销,当然假如需要调度的新线程与当前是同一线程的话,就不需要线程上下文切换了,效率要快很多;
  • GC执行回收时,首先要(安全的)挂起所有线程,遍历所有线程栈(根),GC回收后更新所有线程的根地址,再恢复线程调用,线程越多,GC要干的活就越多;

26.7 使用线程的理由

主要是出于两方面的原因使用线程。

  • 可响应性(通常是对于客户端 GUI 应用程序)
    增强了应用程序的总体使用体验。

  • 性能(对于客户端和服务器应用程序)
    由于 Windows 每个 CPU 调度一个线程,而且多个 CPU 能并发执行这些线程,所以同时执行多个操作能提升性能。

26.8 线程调度和优先级

Windows 之所以被称为抢占式多线程(preemptive multithreaded)操作系统,是因为线程可在任何时间被抢占并调度另一个线程。你在这个方面是有一定控制权的,虽然并不多。

线程优先级

每个线程都分配了从 0(最低)到 31(最高)的优先级。以一种轮流(round-robin)方式调度它们。

Windows 支持 6 个进程优先级类:Idle,Below Normal,Normal,Above Normal,High 和 Realtime。默认的 Normal 是最常用的优先级类。

Windows 支持 7 个相对线程优先级:IdleLowestBelow NormalNormalAbove NormalHighestTime-Critial。这些优先级是相对于进程优先级,所以是最常用的。

表 26-1 “进程优先级类” 和 “相对线程优先级” 如何映射到 “优先级” 值

相对线程优先级 进程优先级类 Idle Below Normal Normal Above Normal High Realtime
Time-Critical 15 15 15 15 15 31
Highest 6 8 10 12 15 26
Above Normal 5 7 9 11 14 25
Noraml 4 6 8 10 13 24
Below Normal 3 5 7 9 12 23
Lowest 2 4 6 8 11 22
Idle 1 1 1 1 1 16

注意,表中没有值为 0 的线程优先级。这是因为 0 优先级保留给零页线程

如果线程要执行长时间的计算限制任务,一般应降低该线程的优先级。
如果线程要快速响应某个事件,运行短暂时间,则应提高该线程的优先级。

26.9 前台线程和后台线程

CLR 将每个线程要么视为前台线程,要么视为后台线程。一个进程的所有前台线程停止运行时,CLR 强制终止仍在运行的任何后台线程。这些后台线程被直接终止:不抛出异常。