线程初步了解 - <第一篇>
操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。
在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());
}
}
输出如下:
线程初步了解 - <第一篇>的更多相关文章
- 转载 线程初步了解 - <第一篇>
操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源.在这些资源之中,会包含一个称为主线程的线程数据结构 ...
- 入住cnblogs第一篇随笔 Hello, world!
在网上搜索计算机参考资料时经常看到各位大神的博客,甚是神往.今天我也在这里安家,记录自己的学习过程,也同各位共勉. 第一篇随笔,就用来测试一下这里的文本编辑器吧. //The C language # ...
- .net开发笔记(十三) Winform常用开发模式第一篇
上一篇博客最后我提到“异步编程模型”(APM),之后本来打算整理一下这方面的材料然后总结一下写篇文章与诸位分享,后来在整理的过程中不断的延伸不断地扩展,发现完全偏离了“异步编程”这个概念,前前后后所有 ...
- RabbitMQ学习总结 第一篇:理论篇
目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...
- Windbg 进程与线程 《第三篇》
Windbg既可以显示进程和线程列表,又可以显示指定进程或线程的详细信息.调试命令可以提供比taskmgr更详尽的进程资料,在调试过程中不可或缺. 一.进程命令 进程命令包括这些内容:显示进程列表.进 ...
- Python开发【第一篇】:目录
本系列博文包含 Python基础.前端开发.Web框架.缓存以及队列等,希望可以给正在学习编程的童鞋提供一点帮助!!! Python开发[第一篇]:目录 Python开发[第二篇]:初识Python ...
- Asp.net原理(第一篇)
Asp.net (第一篇) 当用户在浏览器输入一个URL地址后,浏览器会发送一个请求到服务器.这时候在服务器上第一个负责处理请求的是IIS.然后IIS再根据请求的URL扩展名将请求分发给不同的ISAP ...
- Winform常用开发模式第一篇
Winform常用开发模式第一篇 上一篇博客最后我提到“异步编程模型”(APM),之后本来打算整理一下这方面的材料然后总结一下写篇文章与诸位分享,后来在整理的过程中不断的延伸不断地扩展,发现完全偏离了 ...
- 分布式文件系统 FastDFS 5.0.5 & Linux CentOS 7 安装配置(单点安装)——第一篇
分布式文件系统 FastDFS 5.0.5 & Linux CentOS 7 安装配置(单点安装)--第一篇 简介 首先简单了解一下基础概念,FastDFS是一个开源的轻量级分布式文件系统,由 ...
随机推荐
- PHP CI框架下,如果配置NGINX(根目录和子目录两种模式)
摸索了一会儿,先配置成功,再看看PATH_INFO之类的东东吧. A,根目录: location ~ \.php($|/) { root html; fastcgi_pass ; fastcgi_in ...
- c++ 04
一.this指针 1. 2.应用场景 1) 2)将this指针作为函数的参数.一个对象可以通知另一个对象有关自身的地址. 教师 提问|^ V|答案 学生 交叉类问题: class ...
- JQuery 图片延迟加载并等比缩放插件
原文地址:http://www.shangxueba.com/jingyan/1909987.html DEMO地址:http://demo.jb51.net/html/jquery_img/jque ...
- win10无法使用内置管理员账户打开
对于这种问题其实对于专业版用户倒不是难事,很容易解决,具体方法如下: 首先WIN+R输入:gpedit.msc,打开组策略,找到“计算机配置”里面的“Windows设置”,打开“安全设置” -> ...
- 【转】TI-Davinci开发系列之六CCS5.2调试Linux内核
上转博文<TI-Davinci开发系列之五CCS5.2使用gdbserver远程调试应用程序> 使用CCS5.2远程调试内核时,只需导入Linux内核源码,而不需要编译内核,也就不会用到交 ...
- Direct3D 光照和材质
今天我们来学习下Direct3D里面的光源和材质. 四大光照类型: 环境光 Ambient Light 一个物体没有被光照直接照射,通过每一些物体反射的光线到达这个物体,它也有可能被看到.这种称为 ...
- LitJson处理Json
LitJSON是一个.NET平台下处理JSON格式数据的类库,小巧.快速.它的源代码使用C#编写,可以通过任何.Net平台上的语言进行调用,目前最新版本为LitJSON 0.9. 下载地址: http ...
- UGUI Button和Toogle动态添加事件
如果你想动态创建Button和Toogle 等等一系列控件,需要动态添加事件的如下. 拿button和Toogle抛砖引玉O(∩_∩)O~ using UnityEngine; using Syste ...
- Udp实现简单的聊天程序
在<UDP通讯协议>这篇文章中,简单的说明了Udp协议特征及如何Udp协议传输数据 这里将用Udp协议技术,编写一个简单的聊天程序: //发送端: package com.shindo.j ...
- Java 5 的新标准语法和用法详解集锦
Java 5 的新标准语法和用法详解集锦 Java 5 的新标准语法和用法详解集锦 (需要在首选项-java-complier-compiler compliance level中设置为java5.0 ...