转载 线程初步了解 - <第一篇>
操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。
在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());
}
}

输出如下:

转载 线程初步了解 - <第一篇>的更多相关文章
- 线程初步了解 - <第一篇>
操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源.在这些资源之中,会包含一个称为主线程的线程数据结构 ...
- [转载] Java高新技术第一篇:类加载器详解
本文转载自: http://blog.csdn.net/jiangwei0910410003/article/details/17733153 首先来了解一下字节码和class文件的区别: 我们知道, ...
- [转载] Android Metro风格的Launcher开发系列第一篇
前言:从毕业到现在已经三年多了,回忆一下这三年基本上没有写过博客,总是觉得忙,没时间写,也觉得写博客没什么大用.但是看到很多大牛们都在写博客,分享自己的东西,所以嘛本着向大牛看齐,分享第一,记录第二的 ...
- 转载:eclipse 搭建SSH项目(第一篇)
第一篇:原文地址:http://blog.csdn.net/aaaaaaaa0705/article/details/6288431(虽然没有具体的例子,不过逻辑性强点,比较容易看懂) SSH框架是最 ...
- android调用第三方库——第一篇 (转载)
转自:http://blog.csdn.net/jiuyueguang/article/details/9447245 版权声明:本文为博主原创文章,未经博主允许不得转载. 0:前言: 这两天一直在研 ...
- SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(转载)
SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka(Finchley版本) 转载请标明出处:http://blog.csdn.net/forezp/article/details ...
- IIS负载均衡-Application Request Route详解第一篇: ARR介绍(转载)
IIS负载均衡-Application Request Route详解第一篇: ARR介绍 说到负载均衡,相信大家已经不再陌生了,本系列主要介绍在IIS中可以采用的负载均衡的软件:微软的Applica ...
- [转载]char * 和char []的区别---之第一篇
char * 和char []的区别---之第一篇 原文地址http://blog.csdn.net/yahohi/article/details/7427724 在C/C++中,指针和数组在很多地 ...
- Python开发【第一篇】:目录
本系列博文包含 Python基础.前端开发.Web框架.缓存以及队列等,希望可以给正在学习编程的童鞋提供一点帮助!!! Python开发[第一篇]:目录 Python开发[第二篇]:初识Python ...
随机推荐
- 代码创建 WPF 旋转、翻转动画(汇总)
先建立一个button <Button Width="80" Height="60" Content="旋转" Name=" ...
- asp.net mvc之自定义WebViewPage
采用Razor引擎的View文件最终都会编译成一个WebViewPage类型, 通过自定义WebViewPage,添加相应的属性和方法,你可以很方便的在View里调用, 自定义WebViewPage只 ...
- 【原】通过BeanNameAutoProxyCreator改变臃肿代码
前言: 最近接手了一个项目,大概过了下需求,然后打开项目准备开搞的时候发现一个问题,这个项目是提供rest服务的一个web项目,其中很多旧系统由于还没改成微服务,所以只能通过HttpClient发起调 ...
- elasticsearch6.7 05. Document APIs(1)data replication model
data replication model 本节首先简要介绍Elasticsearch的data replication model,然后详细描述以下CRUD api: 1.读写文档(Reading ...
- 解决Linux服务器tomact-8.0启动慢的问题
环境信息: CentOS release 6.8 tomcat-8.0 JDK1.8 一.启动tomcat #sh /root/tomcat-8.0/bin/startup.sh #tailf /ro ...
- Debian、Ubuntu恢复误删除(或者说重装)的/var/lib/dpkg
在使用ubuntu的使用,有可能会碰到dpkg挂掉,网上的通用解决方法,如果不管用: 1, dpkg 被中断,您必须手工运行 sudo dpkg --configure -a解决此问题 2, sudo ...
- JS之用ES6 Promise解决回调地狱(这里以小程序为例)
首先 写一个请求的方法,如: /** * 银行窗口 * 你需要给我提供相关的相关参数我帮你提交到服务器上 * 我会给你一个等待区的编号给你 你去等待区等待,我处理完成会去等待区通知你 * @param ...
- ArcGIS for Server 的修改IP问题
ArcGIS for Server 的修改IP问题 1. [arcgisserver@centos6 ~]$ vi /home/arcgisserver/serverconfig/config ...
- B-树、B+树
B-树 用来在外部存储中组织数据. 严格来说,2-3树.2-3-4树都是B-树的特例:但B树更强调它的节点有很多个子节点,B-树中的节点可以有几十或几百个子节点. B-树也可以是查找树,也可以不是查找 ...
- 超简单,Centos7 安装 rabbitMQ
首先声明,本人是Linux新手一枚.经历了在阿里云Centos上部署rabbitMQ与重装的痛苦经历,后多方查找终于找到了简单方法.Linux高人来说请跳过本篇文章,新手可以试试. 1.设置Cento ...