转载 线程初步了解 - <第一篇>
操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。
在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 ...
随机推荐
- [转]TFS2010 Team Project Collections
本文转自:https://www.cnblogs.com/shanyou/archive/2010/04/14/1712252.html Team Foundation Server 2010有一个改 ...
- 【表格设置】HTML中合并单元格,对列组合应用样式,适应各浏览器的内容换行
1.常用表格标签 普通 <table> | <tr> | | <th ...
- webAPI文件上传时文件过大404错误的问题
背景:最近公司有个需求,外网希望自动保存数据到内网,内网有2台服务器可以相互访问,其中一台服务器外网可以访问,于是想在 这台服务器上放个中转的接口.后来做出来以后测试发现没有问题就放线上去了,不顾发现 ...
- C# Aspose.Cells控件读取Excel
Workbook workbook = new Workbook(); workbook.Open("C:\\test.xlsx"); Cells cells = workbook ...
- (1)Jquery1.8.3快速入门_helloworld
jquery 快速入门 学习记录 1.第一个jquery程序 helloworld: 需要导入Jquery1.8.3的库 ,下载地址: jQuery1.8.3 代码令另存到本地 保存为jquery ...
- PyCharm 添加签名和时间
工具栏上添加上 Toolbar 点击 Editor -> File and Code Templates -> Python Script 在文本框上填写需要的数据
- 【代码笔记】Web-HTML-基础
一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- 【读书笔记】iOS-解析JSON
JSON相比XML最显著的优点是不需要使用重量级的解析库,因为其本身就是面向数据的,而且非常容易转换成哈希字典.除此之外,JSON文档相比同样的XML文档更小.在网络宽带有限的情况下,你很容易在Iph ...
- JS输入框正则校验
1. 开发中需要对etl组件统一进行input输入框校验,允许为空,可以不校验,默认校验长度和特殊字符,代码如下,记录以备复用. /** * 数据值校验工具类 */ var checkService ...
- Puppet的搭建和应用
Puppet的部署与应用 1. 案例概述 作为一名系统管理员,维护服务器正常运行是最基本的职责,在管理几台到几十台服务器时,大部分管理员喜欢自己写小工具来维护,但随着服务器的数量曾多,任务量也逐渐增多 ...