操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。

  在Windows操作系统下,线程的的数据结构包含以下内容:

  1、线程的核心对象:主要包含线程当前的寄存器状态,当操作系统调度这个线程开始运行的时候,寄存器的状态将被加载到CPU中,重新构建线程的执行环境,当线程被调度出来的时候,最后的寄存器状态被重新保存到这里,已备下一次执行的时候使用。
  2、线程环境块(Thread Environment Block,TED):是一块用户模式下的内存,包含线程的异常处理链的头部。另外,线程的局部存储数据(Thread Local Storage Data)也存在这里。
  3、用户模式的堆栈:用户程序的局部变量和参数传递所使用的堆栈,默认情况下,Windows将会被分配1M的空间用于用户模式堆栈。
  4、内核模式堆栈:用于访问操作系统时使用的堆栈。

  在抢先式多任务的环境下,在一个特定的时间,CPU将一个线程调度进CPU中执行,这个线程最多将会运行一个时间片的时间长度,当时间片到期之后,操作系统将这个线程调度出CPU,将另外一个线程调度进CPU,我们通常称这种操作为上下文切换。

  在每一次的上下文切换时,Windows将执行下面的步骤:

  • 将当前的CPU寄存器的值保存到当前运行的线程数据结构中,即其中的线程核心对象中。
  • 选中下一个准备运行的线程,如果这个线程处于不同的进程中,那么,还必须首先切换虚拟地址空间。
  • 加载准备运行线程的CPU寄存器状态到CPU中。

  公共语言运行时CLR(Common Language Runtime)是.Net程序运行的环境,它负责资源管理,并保证应用和底层操作系统之间必要的分离。

  在.Net环境下,CLR中的线程需要通过操作系统的线程完成实际的工作,目前情况下,.Net直接将CLR中的线程映射到操作系统的线程进行处理和调度,所以,我们每创建一个线程将会消耗1M以上的内存空间。但未来CLR中的线程并不一定与操作系统中的线程完全对应。通过创建CLR环境下的逻辑线程,我们可能创建更加节省资源的线程,使得大量的CLR线程可以工作在少量的操作系统线程之上。

一、线程的定义

  在单CPU系统的一个单位时间(time slice)内,CPU只能运行单个线程,运行顺序取决于线程的优先级别。如果在单位时间内线程未能完成执行,系统就会把线程的状态信息保持到线程的本地存储器(TLS)中,以便下次执行时恢复执行。而多线程只是系统带来的一个假象,它在多个单位时间内进行多个线程的切换,因为切换频密而且单位时间非常短暂,所以多线程被视作同时运行。

  适当使用多线程能提高系统的性能,比如:在系统请求大容量的数据时使用多线程,把数据输出工作交给异步线程,使主线程保持其稳定性去处理其他问题。但需要注意一点,因为CPU需要花费不少的时间在线程的切换上,所以过多地使用多线程反而会导致性能的下降。

  1、System.Threading 命名空间中的常用类

  在System.Threading命名空间内提供多个方法来构建多线程应用程序,其中ThreadPool与Thread是多线程开发中最常用到的,在.NET中专门设定了一个CLR线程池专门用于管理线程的运行,这个CLR线程池正是通过ThreadPool类来管理,而Thread是管理线程的最直接方式。

说明
AutoResetEvent 通知正在等待的线程已发生事件
ManualResetEvent 通知正在等待的线程已发生事件
Interlocked 为多个线程共享的变量提供原子操作
Monitor 提供同步对对象的访问的机制
Mutex 一个同步基元,也可用于进程间同步
Thread 创建并控制线程,设置其优先级并获取其状态
ThreadPool  提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器
WaitHandle 封装等待对共享资源的独占访问的操作系统特定的对象
ReadWriterLock 读写锁
Semaphore 控制线程的访问数量

二、线程的优先级

  理解有误,示例并不正确;

  为了方便线程的管理,线程有个优先级,优先级用于决定哪个线程优先执行,在Thread对象中就是Priority属性。

  优先级由低到高分别是:

优先级 说明
Lowest 可以将 Thread 安排在具有任何其他优先级的线程之后
BelowNormal 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前
Normal 默认值。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前
AboveNormal 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前
Highest 可以将 Thread 安排在具有任何其他优先级的线程之前

  先来看一个优先级的示例:

    class Program
{
static void Main(string[] args)
{
//新建3个线程并设定各自的优先级
Thread t1 = new Thread(Run);
t1.Priority = ThreadPriority.Lowest;
Thread t2 = new Thread(Run);
t2.Priority = ThreadPriority.Normal;
Thread t3 = new Thread(Run);
t3.Priority = ThreadPriority.Highest;
//由低到高优先级的顺序依次调用
t1.Start();
t2.Start();
t3.Start();
Console.ReadKey();
} public static void Run()
{
Console.WriteLine("我的优先级是:" + Thread.CurrentThread.Priority);
}
}

  来看输出:

  

  留意到线程是按照优先级的顺序执行的。

三、常用属性

常用属性 说明
CurrentThread 获取当前正在运行的线程
IsAlive 获取一个值,该值指示当前线程的执行状态
IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程, 后台线程会随前台线程的关闭而退出
IsThreadPoolThread 获取一个值,该值指示线程是否属于托管线程池
ManagedThreadId 获取当前托管线程的唯一标识符
Name 获取或设置线程的名称
Priority 获取或设置一个值,该值指示线程的调度优先级
ThreadState 获取一个值,该值包含当前线程的状态

  ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。不能够通过Name,因为Name只是一个简单的属性,可以随便改,不能保证无重复。

  常用属性示例:

    class Program
{
static void Main(string[] args)
{
//新建3个线程并设定各自的优先级
Thread t1 = new Thread(Run);
t1.Priority = ThreadPriority.Normal;
t1.Start(); Console.ReadKey();
} public static void Run()
{
Thread t1 = Thread.CurrentThread; //静态属性,获取当前执行这行代码的线程
Console.WriteLine("我的优先级是:" + t1.Priority);
Console.WriteLine("我是否还在执行:" + t1.IsAlive);
Console.WriteLine("是否是后台线程:" + t1.IsBackground);
Console.WriteLine("是否是线程池线程:" + t1.IsThreadPoolThread);
Console.WriteLine("线程唯一标识符:" + t1.ManagedThreadId);
Console.WriteLine("我的名称是:" + t1.Name);
Console.WriteLine("我的状态是:" + t1.ThreadState);
}
}

  输出如下:

  

  1、前台线程与后台线程的区别

  我们看到上面有个属性叫后台线程,非后台线程就叫前台线程吧,Thread.Start()启动的线程默认为前台线程,启动程序时创建的主线程一定是前台线程。应用程序与必须等到所有的前台线程执行完毕才会卸载。而当IsBackground设置为true时,就是后台线程了,当主线程执行完毕后就直接卸载,不再理会后台线程是否执行完毕。

  前台与后台线程的设置必须在线程启动之前进行设置,线程启动之后就不能设置了。

  Thread创建的线程是前台线程,线程池中的是后台线程。

    class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run);
t1.IsBackground = true; //设为后台线程
t1.Start();
Console.WriteLine("不等你咯,后台线程!"); //注意这里不要Console.Readxxx();,让控制台执行完毕就自动关闭
} public static void Run()
{
Thread.Sleep(5000);
Console.WriteLine("后台线程正在执行!");
}
}

  前台线程与后台线程的区别如下,上面的示例没法用图片来说明,简要说发生的情况。当t1设置为前台线程时,5秒后,控制台窗口才关闭。如果t1设置为后台线程,则窗口瞬间就关闭了。

  2、ThreadState的状态

  对于ThreadState的值有以下几种:

线程状态 说明
Aborted 线程已停止
AbortRequested 线程的Thread.Abort()方法已被调用,但是线程还未停止
Background 线程在后台执行,与属性Thread.IsBackground有关
Running 线程正在正常运行
Stopped 线程已经被停止
StopRequested 线程正在被要求停止
Suspended 线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行)
SuspendRequested 线程正在要求被挂起,但是未来得及响应
Unstarted 未调用Thread.Start()开始线程的运行
WaitSleepJoin 线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态

  线程在以上几种状态的切换如下:

  刚刚创建的线程处于已经准备好运行,但是还没有运行的状态,称为Ready(准备)状态。在操作系统的调度之下,这个线程可以进入(Runing)运行状态。运行状态的线程可能因为时间片用完的缘故被操作系统切换出CPU,称为Suspended(暂停运行)状态,也可能在时间片还没有用完的情况下,因为等待其他优先级更高的任务,而转换到Blocked(阻塞)状态。在阻塞状态下的线程,随时可以因为再次调度而重新进入运行状态。线程还可能通过Sleep方法进入Sleep(睡眠)状态,当睡眠时间到期之后,可以再次被调度运行。处于运行状态的线程还可能被主动终止执行,直接结束;也可能因为任务已经完成,被操作系统正常结束。

四、方法

方法 说明
Abort 终止线程
GetDomain 当前线程运行在的应用程序域
GetDomainID 唯一的应用程序域标识符
Interrupt 中断处于 WaitSleepJoin 线程状态的线程
Join 阻塞调用线程,直到某个线程终止时为止
ResetAbort 取消为当前线程请求的 Abort
Sleep 将当前线程阻塞指定的毫秒数
SpinWait 导致线程等待由 iterations 参数定义的时间量
Start  启动线程以按计划执行

1、Join串行执行

Join,串行执行,相当于ajax里面的async:false

    class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run);
t1.Name = "t1";
t1.Start();
t1.Join(); //等待t1执行完之后,主线程再执行,线程间的关系为串行,非并行
Console.WriteLine("主线程执行这了么?");
Console.ReadKey();
} public static void Run()
{
Console.WriteLine("线程" + Thread.CurrentThread.Name + "开始执行!");
Thread.Sleep(5000);
Console.WriteLine("线程" + Thread.CurrentThread.Name + "执行完毕!");
}
}

  输出:

  

  2、Interrupt 与 Abort

  Interrupt和Abort:这两个关键字都是用来强制终止线程,不过两者还是有区别的。

1、Interrupt:  抛出的是 ThreadInterruptedException 异常。

     Abort:  抛出的是  ThreadAbortException 异常。

2、Interrupt:如果终止工作线程,只能管到一次,工作线程的下一次sleep就管不到了,相当于一个contine操作。如果线程正在sleep状态,则通过Interrypt跳过一次此状态也能够达到唤醒效果。

     Abort:这个就是相当于一个break操作,工作线程彻底停止掉。 当然,你也已在catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。

    class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run);
t1.Start();
//当Interrup时,线程已进入for循环,中断第一次之后,第二次循环无法再停止
t1.Interrupt(); t1.Join();
Console.WriteLine("============================================================"); Thread t2 = new Thread(Run);
t2.Start();
//停止1秒的目的是为了让线程t2开始,否则t2都没开始就直接中止了
Thread.Sleep(1000);
//直接终止掉线程,线程被终止,自然无法输出什么!
t2.Abort(); Console.ReadKey();
} static void Run()
{
for (int i = 1; i <= 5; i++)
{
try
{
//连续睡眠5次
Thread.Sleep(2000);
Console.WriteLine("第" + i + "次Sleep!");
}
catch (Exception e)
{
Console.WriteLine("第" + i + "次Sleep被中断!" + " " + e.Message);
}
}
}
}

  输出:

  

  3、Suspend 与 Resume (慎用)

  Thread.Suspend()与 Thread.Resume()是在Framework1.0 就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend()使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。在MSDN中,这两个方法也已被标记为已过时。

五、ThreadStart委托

  ThreadStart所生成并不受线程池管理。

  通过ThreadStart委托启动线程:

    class Program
{
static void Main(string[] args)
{
Console.WriteLine("主线程Id是:" + Thread.CurrentThread.ManagedThreadId);
Message message = new Message();
Thread thread = new Thread(new ThreadStart(message.ShowMessage));
thread.Start();
Console.WriteLine("正在做某事......");
Console.WriteLine("主线程工作完成!"); Console.ReadKey();
} public class Message
{
public void ShowMessage()
{
string message = string.Format("异步线程Id是:{0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(message);
for (int i = 0; i < 10; i++)
{
Thread.Sleep(300);
Console.WriteLine("异步线程当前循环执行到" + i);
}
}
}
}

  输出:

  

六、ParameterizedThreadStart委托

  ParameterizeThreadStart委托于ThreadStart委托非常相似,但ParameterizedThreadStart委托是面向带参数方法的。注意ParameterizedThreadStart对应方法的参数为object。

    class Person
{
public Person(string name, int age){ this.Name = name;this.Age = age; }
public string Name { get; set; }
public int Age { get; set; }
} class Program
{
static void Main(string[] args)
{
//整数作为参数
for (int i = 0; i < 2; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(Run));
t.Start(i);
}
Console.WriteLine("主线程执行完毕!"); //自定义类型作为参数
Person p1 = new Person("关羽", 22);
Person p2 = new Person("张飞", 21);
Thread t1 = new Thread(new ParameterizedThreadStart(RunP));
t1.Start(p1);
Thread t2 = new Thread(new ParameterizedThreadStart(RunP));
t2.Start(p2); Console.ReadKey();
} public static void Run(object i)
{
Thread.Sleep(50);
Console.WriteLine("线程传进来的参数是:" + i.ToString());
} public static void RunP(object o)
{
Thread.Sleep(50);
Person p = o as Person;
Console.WriteLine(p.Name + p.Age);
}
}

  输出:

  

七、TimerCallback委托

  TimerCallback委托专门用于定时器的操作,这个委托允许我们定义一个定时任务,在指定的间隔之后重复调用。实际的类型与ParameterizedThreadStart委托是一样的。

  Timer类的构造函数定义如下:

  public Timmer(TimerCallback callback,Object state,long dueTime,long period)
  • Callback表示一个时间到达时执行的委托,这个委托代表的方法必须符合委托TimerCallback的定义。
  • State表示当调用这个定时器委托时传递的参数。
  • dutTime表示从创建定时器到第一次调用时延迟的时间,以毫秒为单位。
  • Period表示定时器开始之后,每次调用之间的时间间隔,以毫秒为单位。

  示例,使用TimerCallback每隔一秒钟输出一次时间:

    class Program
{
static void Main(string[] args)
{
System.Threading.Timer clock = new System.Threading.Timer(ConsoleApplication1.Program.ShowTime, null, 0, 1000); Console.ReadKey();
} public static void ShowTime(object userData)
{
Console.WriteLine(DateTime.Now.ToString());
}
}

  输出如下:

  

转载 线程初步了解 - <第一篇>的更多相关文章

  1. 线程初步了解 - <第一篇>

    操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源.在这些资源之中,会包含一个称为主线程的线程数据结构 ...

  2. [转载] Java高新技术第一篇:类加载器详解

    本文转载自: http://blog.csdn.net/jiangwei0910410003/article/details/17733153 首先来了解一下字节码和class文件的区别: 我们知道, ...

  3. [转载] Android Metro风格的Launcher开发系列第一篇

    前言:从毕业到现在已经三年多了,回忆一下这三年基本上没有写过博客,总是觉得忙,没时间写,也觉得写博客没什么大用.但是看到很多大牛们都在写博客,分享自己的东西,所以嘛本着向大牛看齐,分享第一,记录第二的 ...

  4. 转载:eclipse 搭建SSH项目(第一篇)

    第一篇:原文地址:http://blog.csdn.net/aaaaaaaa0705/article/details/6288431(虽然没有具体的例子,不过逻辑性强点,比较容易看懂) SSH框架是最 ...

  5. android调用第三方库——第一篇 (转载)

    转自:http://blog.csdn.net/jiuyueguang/article/details/9447245 版权声明:本文为博主原创文章,未经博主允许不得转载. 0:前言: 这两天一直在研 ...

  6. SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(转载)

    SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(Finchley版本) 转载请标明出处:http://blog.csdn.net/forezp/article/details ...

  7. IIS负载均衡-Application Request Route详解第一篇: ARR介绍(转载)

    IIS负载均衡-Application Request Route详解第一篇: ARR介绍 说到负载均衡,相信大家已经不再陌生了,本系列主要介绍在IIS中可以采用的负载均衡的软件:微软的Applica ...

  8. [转载]char * 和char []的区别---之第一篇

    char *  和char []的区别---之第一篇 原文地址http://blog.csdn.net/yahohi/article/details/7427724 在C/C++中,指针和数组在很多地 ...

  9. Python开发【第一篇】:目录

    本系列博文包含 Python基础.前端开发.Web框架.缓存以及队列等,希望可以给正在学习编程的童鞋提供一点帮助!!! Python开发[第一篇]:目录 Python开发[第二篇]:初识Python ...

随机推荐

  1. [转]TFS2010 Team Project Collections

    本文转自:https://www.cnblogs.com/shanyou/archive/2010/04/14/1712252.html Team Foundation Server 2010有一个改 ...

  2. 【表格设置】HTML中合并单元格,对列组合应用样式,适应各浏览器的内容换行

    1.常用表格标签 普通    <table>           |           <tr>          |           |          <th ...

  3. webAPI文件上传时文件过大404错误的问题

    背景:最近公司有个需求,外网希望自动保存数据到内网,内网有2台服务器可以相互访问,其中一台服务器外网可以访问,于是想在 这台服务器上放个中转的接口.后来做出来以后测试发现没有问题就放线上去了,不顾发现 ...

  4. C# Aspose.Cells控件读取Excel

    Workbook workbook = new Workbook(); workbook.Open("C:\\test.xlsx"); Cells cells = workbook ...

  5. (1)Jquery1.8.3快速入门_helloworld

    jquery 快速入门 学习记录 1.第一个jquery程序 helloworld: 需要导入Jquery1.8.3的库 ,下载地址: jQuery1.8.3   代码令另存到本地 保存为jquery ...

  6. PyCharm 添加签名和时间

    工具栏上添加上 Toolbar 点击 Editor -> File and Code Templates -> Python Script 在文本框上填写需要的数据

  7. 【代码笔记】Web-HTML-基础

    一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  8. 【读书笔记】iOS-解析JSON

    JSON相比XML最显著的优点是不需要使用重量级的解析库,因为其本身就是面向数据的,而且非常容易转换成哈希字典.除此之外,JSON文档相比同样的XML文档更小.在网络宽带有限的情况下,你很容易在Iph ...

  9. JS输入框正则校验

    1. 开发中需要对etl组件统一进行input输入框校验,允许为空,可以不校验,默认校验长度和特殊字符,代码如下,记录以备复用. /** * 数据值校验工具类 */ var checkService ...

  10. Puppet的搭建和应用

    Puppet的部署与应用 1. 案例概述 作为一名系统管理员,维护服务器正常运行是最基本的职责,在管理几台到几十台服务器时,大部分管理员喜欢自己写小工具来维护,但随着服务器的数量曾多,任务量也逐渐增多 ...