26.2线程开销
线程要素
-
线程内核对象(thread kemel object)
每个线程都有一个数据结构,数据结构包含一组属性与线程上下文(threadContext),上下文包含CPU寄存器集合的内存块。
-
线程环境块(thread environment block,TEB)
TEB是在用户模式中分配和初始化的内存块。
-
用户模式栈(user-mode stack)
用户模式栈用于存储传给方法的局部变量和实参。他还包含一个地址:指出当前方法返回时,线程应该从什么地方开始执行。
-
内核模式栈(kernel-mode stack)
应用程序代码向操作系统的内核模式函数传输实参时,出于安全考虑,Windows都会把他们从线程的用户模式栈复制到线程的内核模式栈。
-
DLL线程链接(attach)和线程分离(detach)通知
任何时候在进程中创建线程,都会调用进程中加载的所有非托管DLL的DllMain方法,并向该方法传递DLL_THREAD_ATTACH标志,同理线程终止时会向方法传递DLL_THREAD_DETACH标志。
C# 和其他大多数托管编程语言生成的DLL没有DLLMain函数。所以,托管DLL不会收到标志通知,这提升了性能。
非托管DLL可以调用
Win32DisableThreadLibraryCalls
函数来决定不理会这些通知。
上下文切换
由来:
一个单CUP一次只能做一件事情,所以Windows任何时刻只将一个线程分配给一个CPU。那个线程只能运行一个“时间片”(大约是30毫秒)的长度。时间到期,Windows就上下文切换到另一个线程。
操作:
-
将CPU寄存器的值保存到当前正在运行的线程的内核对象内部的一个上下文结构中。
-
从现有线程集合中选择一个线程供调用。
-
将所选的上下文结构中的值加载到CPU的寄存器中。
一个时间片结束,如果Windows决定再次调度同一个线程,则不会执行上下文切换。
线程可自主提前终止其时间片。
多线程性能:
-
上下文切换所需的时间取决于CPU架构和速度。
-
上下文切换是净开销,不会换来任何内存或性能上的收益。
-
执行垃圾回收时,CLR必须挂起所有线程。
-
使用调试器并遇到断点时,Windows会挂起正在调试的应用程序的所有线程。
多线程的优势:
1.现如今都是多核处理器,意味着多线程可以更好的利用CPU资源。真正的同时运行多个线程。
2.每个进程都有它自己的线程,可以避免发生死循环的应用程序妨害其他应用程序执行。(避免一个程序卡死,导致整个系统死机)
26.3停止疯狂
简易的来说我们知道应用程序在设计时可能设计多个甚至上百个线程,在运行中这些线程大多数不会充分使用时间片,往往提前终止了时间片,因为它们当前没有要做的事情,大多数是在等待事件的触发(例如记事本等待用户按键输入)。虽然线程没有占用CUP太多时间,但是太多的上下文切换明显是对性能的浪费,且每个线程在创立时便会被分配一部分内存,这些内存也是对资源的浪费。
26.4CPU发展趋势
-
多个CUP
简单举例就是服务器所用到的主板,可以在一台电脑上安装多个CPU。
-
超线程芯片
Intel专利技术,允许一个芯片在操作系统中显示成两个。对于Windows,看起来像是安装了两个CPU,但实际上是在一个CUP上运行的。
-
多核芯片
当前CUP的常规设计,即CUP参数上写的多核。
26.6使用专用线程执行异步的计算限制操作
本节将展示如何创建线程来执行异步的计算限制(compute-bound)操作。
强烈建议避免使用这个技术,取而代之的应尽量使用线程池来执行异步的计算限制操作。
using System;
using System.Threading;
public static class ThreadBasics
{
public static void Main()
{
FirstThread.Go();
}
}
internal static class FirstThread
{
public static void Go()
{
Console.WriteLine("主线程:启动一个专用的线程 " +
"执行异步操作");
Thread dedicatedThread = new Thread(ComputeBoundOp);
dedicatedThread.Start(5);
Console.WriteLine("主线程:在这里做其他工作…");
Thread.Sleep(10000); // 模拟其他工作(10秒)
dedicatedThread.Join(); // 等待线程终止
Console.ReadLine();
}
// 此方法的签名必须与ParametizedThreadStart委托匹配
private static void ComputeBoundOp(Object state)
{
//此方法由一个专用线程执行
Console.WriteLine("In ComputeBoundOp: state={0}", state);
Thread.Sleep(1000); // 模拟其他工作(1秒)
//当此方法返回时,专用线程终止
}
}
26.8线程调度和优先级
优先级基本属性
每个线程都分配了从0(最低)到31(最高)的优先级。
较高的优先级线程总是抢占较低优先级的线程。如果有一个5级的线程在运行,而系统确定有一个较高优先级的线程准备号运行,系统会立即挂起较低优先级的线程(即使当前线程的时间片还没有用完)。
-
零页线程(zero page thread)
系统在启动是会自动创建一个零页线程,该线程的优先级是系统中唯一一个优先级为0的线程
-
优先级类(priority class,指当前进程的优先级)
Windows支持六个进程优先级类:Idle,Below Normal,Normal,Above Normal,High,Realtime
默认的Normal是常用的优先级类
Idle适合系统没有事情做时候运行的程序(比如屏幕保护程序)
High只有绝对必要的时候才能应用
Realtime尽可能避免使用,它的级别可能干扰操作系统任务
-
优先级(指当前进程下线程的优先级)
Windows默认支持7个相对线程优先级:Idle,Lowest,Below Normal,Normal,AboveNormal,Highest,Time-Critical
-
基础优先级(base priority)
基础优先级是经过优先级类与优先级合并映射得到的一个优先级
-
动态优先级(dynamic priority)
动态优先级是在基础优先级的基础上,用户调整优先级类或优先级后得到的一个优先级
优先级修改:
通过设置Thread的Priority属性,向其传递ThreadPriority枚举可以修改线程的相对线程优先级;
ThreadPriority枚举定义:Lowest,Below Normal,Normal,AboveNormal,Highest
注意:在修改优先级时最好时降低一个线程的优先级,而不是提升另一个线程的优先级。
26.9前台线程和后台线程
CLR将每个线程划分为两种:前台线程、后台线程
一个进程的所有前台线程停止运行时,CLR会强制终止仍在运行的任何后台线程。这些后台线程会被直接终止;不会抛出异常。
后台线程设置演示:
using System;
using System.Threading;
public static class ThreadBasics
{
public static void Main()
{
BackgroundDemo.Go(true);
BackgroundDemo.Go(false);
}
}
internal static class BackgroundDemo
{
public static void Go(Boolean background)
{
// 创建一个新线程(默认为前台)
Thread t = new Thread(new ThreadStart(ThreadMethod));
// 如果需要,可以将线程设置为背景线程
if (background)
t.IsBackground = true;
t.Start(); // 启动线程
return; // 注意:应用程序实际上不会在10秒内死亡
}
private static void ThreadMethod()
{
Thread.Sleep(10000); // 模拟10秒钟的工作
Console.WriteLine("线程方法退出");
}
}