Thread
来源:互联网 发布:sql中时间戳转换 编辑:程序博客网 时间:2024/06/04 00:26
1,thread本身来说是操作系统的概念。
thread的内核数据结构,其中有osid(操作系统ID) context(保存CPU寄存器里面的一些变量)
2,thread环境快
tls(thread本地存储),execptionList 的信息
3,用户模式堆栈
一个线程分配1M堆栈空间(这个空间里放局部变量,或者说放参数)
4,内核模式堆栈
在CLR的线程操作,包括线程同步,大多都是调用底层的win32函数
我们的用户模式堆栈的参数,需要传递到内核模式。
二:时间上的开销
我们进程启动的时候,会加载很多的dll【托管的和非托管的】exe,资源,元数据...
进程启动的时候默认会有三个应用程序域,1,system domain, 2,shared domain 3,domain1
开启一个线程,或者销毁一个线程都会通知进程中的dll,因为它会给它一个标识位(attach,detach)
通知dlL的目的就是给thread做准备工作,比如销毁,让这些dll做资源清理
线程的生命周期管理
在CLR中Thread这个名字用来表示线程,线程的生命周期有几种状态
1,Start()线程开启
2,Suspend()线程挂起(暂停)
3,Resume() 继续已挂起的线程(将一个挂起线程复活继续执行)
4,Interrupt() 中断处于 WaitSleepJoin 线程状态的线程。 相当于while(true){continue;}的效果
4,Abort() 线程销毁 (终止线程)相当于while(true){break;}的效果
注解:调用 Thread.Sleep(System.Int32) 或 Thread.Join()方法后ThreadState的值为WaitSleepJoin
Thread中的静态方法
线程可见的三种方式
线程可见的三种方式: 即一个变量,对于不同的线程是否可见(是否可见的意思,即thread是否可以读取这个变量,或者说是否共享)
比如说:int a 这个变量,对于threadA 和threadB 都是共享的
又比如:int b 这个变量,对于threadA可见,对threadB不可见
第一种:数据槽位AllocateNamedDataSlot
class Program{ static void Main(string[] args) { //给所有线程分配一个数据槽位,这个槽位的名称叫username,用于存放数据 var slot = Thread.AllocateNamedDataSlot("username"); //在主线程上设置一个slot槽位,并在这个槽位中写入一个 hello word数据 //这个“hello word”就只能被主线程读取 Thread.SetData(slot, "hello word"); //这个线程是主线程 var t = new Thread(() => { //这个线程是子线程 //子线程无法读取设置在主线程上的slot槽位数据 var childdata = Thread.GetData(slot); Console.WriteLine(childdata); //什么也没有输出 }); t.Start(); var mainSlot = Thread.GetData(slot);//主线程能获取到存储在主线程槽位上的“hello word” Console.WriteLine(mainSlot); //输出hello word Console.ReadKey(); }}~
class Program { static void Main(string[] args) { //给所有线程分配一个数据槽位,这个槽位的名称叫username,用于存放数据 var slot = Thread.AllocateNamedDataSlot("username"); //var solt = Thread.AllocateDataSlot();//当然也可以用不命名的槽位(数据槽) //在主线程槽位上存储一个数据(只能被主线程读取到) Thread.SetData(slot, "我是存储在主线程槽位上的数据"); //这个线程是主线程 var t = new Thread(() => { //在子线程槽位上存储一个数据(只能被子线程读取到) Thread.SetData(slot, "我是存储在子线程槽位上的数据"); var childdata = Thread.GetData(slot); Console.WriteLine(childdata); //输出:我是存储在子线程槽位上的数据 }); t.Start(); var mainSlot = Thread.GetData(slot); Console.WriteLine(mainSlot); //输出:我是存储在主线程槽位上的数据 Console.ReadKey(); }}
第二种:[ThreadStatic]
class Program{ //打了ThreadStatic特性标签的: 指示静态字段的值对于每个线程都是唯一的。 [ThreadStatic] static string mainStr = "我是主线程上的数据"; static void Main(string[] args) { var t = new Thread(() => { Console.WriteLine("子线程:" + mainStr); //在子线程上读取不到mainstr }); t.Start(); Console.WriteLine(mainStr); //输出:我是主线程上的数据 Console.ReadKey(); }}
~
class MyStaticFiledClass{ //一般静态变量 static int X = 0; //有ThreadStatic标记的静态变量 [ThreadStatic] static int threadStaticX = 0; // 分别对x,y 自增 public void AddXY() { for (int i = 0; i < 10; i++) { X++; threadStaticX++; Thread current = Thread.CurrentThread; string info = string.Format("threadID:{0} x={1}; threadStaticX={2}", current.ManagedThreadId, X, threadStaticX); Console.WriteLine(info); Thread.Sleep(500); } }}class Program{ static void Main(string[] args) { MyStaticFiledClass myStaticTest = new MyStaticFiledClass(); ThreadStart ts1 = new ThreadStart(myStaticTest.AddXY); Thread t1 = new Thread(ts1); Thread t2 = new Thread(ts1); t1.Start(); t2.Start(); Console.ReadKey(); }}
第三种:ThreadLocal
class Program{ static void Main(string[] args) { ThreadLocal<string> local = new ThreadLocal<string>(); local.Value = "我是主线程上的数据"; var t = new Thread(() => { Console.WriteLine("子线程:" +local.Value); //子线程是拿不到设置在主线程上的local数据的 }); t.Start(); Console.WriteLine("主线程:" + local.Value); //主线程可以拿到设置在主线程上的local Console.ReadKey(); }}
内存栏栅
问题
一般我们发布项目的时候通常都会采用release版本,因为Release会在jit层面对我们的il代码进行了优化,比如在迭代和内存操作的性能提升方面
Release版本比Debug版本性能提升能达到5倍
Release确实是一个非常好的东西,但是在享受好处的同时也不要忘了,任何优化都是要付出代价的,这世界不会什么好事都让你给占了,release有时候为了
性能提升,会大胆的给你做一些代码优化和cpu指令的优化,比如说把你的一些变量和参数缓存在cpu的高速缓存中,不然的话,你的性能能提升这么多么~~~
绝大多数情况下都不会遇到问题,但有时你很不幸,要出就出大问题,下面我同样举一个例子给大家演示一下:
class Program{ static void Main(string[] args) { //主线程也工作线程t共享这个变量isStop, var isStop = false; var task = Task.Factory.StartNew(() => { var isSuccess = false; //在Release环境下 while (!isStop) { isSuccess = !isSuccess; } }); Thread.Sleep(1000); isStop = true; task.Wait(); Console.WriteLine("主线程执行结束!"); Console.ReadKey(); }}
上面这串代码的意思很简单,我就不费劲给大家解释了,但是有意思的事情就是,这段代码在debug和release的环境下执行的结果却是天壤之别,而我们的常规
思想其实就是1ms之后,主线程执行console.writeline(...)对吧,而真相却是:debug正常输出,release却长久卡顿。。。。一直wait啦。。。。这是一个大bug啊。。。不信的话你可以看看下面的截图嘛。。。
Debug下Release下
刚才也说过了,release版本会在jit层面对il代码进行优化,所以看应用程序的il代码是看不出什么名堂的,但是可以大概能猜到的就是,要么jit直接把代码
while (!isStop){ isSuccess = !isSuccess;}优化成了
while (true){ isSuccess = !isSuccess;}
要么就是为了加快执行速度,mainThread和task会将isStop变量从memory中加载到各自的cpu缓存中,而主线程执行isStop=true的时候而task读的还是cpu
缓存中的脏数据,也就是还是按照isStop=false的情况进行执行。
三种解决方案
1:volatile
那这个问题该怎么解决呢?大家第一个想到的就是volatile关键词,这个关键词我想大家都知道有2个意思:
<1>. 告诉编译器,jit,cpu不要对我进行任何形式的优化,谢谢。
<2>. 该变量必须从memory中读取,而不是cpu cache中。
所以可以将上面的代码优化成如下方式,问题就可以完美解决:2: Thread.MemoryBarrier
class Program{ //在变量前面加上volatile static bool isStop = false; static void Main(string[] args) { var task = Task.Factory.StartNew(() => { var isSuccess = false; while (!isStop) { //这段代码的意思是:在此方法之前内存写入都要及时从CPU 缓存中更新到内存 //在此方法之后内存读取都要用memory(内存)中读取,而不是CPU缓存 Thread.MemoryBarrier(); isSuccess = !isSuccess; } }); Thread.Sleep(1000); isStop = true; task.Wait(); Console.WriteLine("主线程执行结束!"); Console.ReadKey(); }}
3:Thread.VolatileRead
class Program{ static void Main(string[] args) { int isStop = 0; var task = Task.Factory.StartNew(() => { var isSuccess = false; while (isStop!=1) { //每次循环都要从内存中读取 "isStop" 的最新值 Thread.VolatileRead(ref isStop); isSuccess = !isSuccess; } }); Thread.Sleep(1000); isStop = 1; task.Wait(); Console.WriteLine("主线程执行结束!"); Console.ReadKey(); }}
ThreadPool
线程池的作用:
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。
线程池的定时器功能
//ThreadPool 有一个定时器的功能class Program{ static void Main(string[] args) { //第一个参数:WaitHandle是一个抽象类 其中AutoResetEvent类是继承自WaitHandle的【这个AutoResetEvent类的参数是一个布尔值,如果为true表示立即执行,如果为false表示根据定时器的设定的多少毫秒调用一次来做延迟执行,例如我在这个定时器的第四个参数设定的值是2000毫秒,那么这个定时器就延迟2000毫秒开始执行】 //第二个参数委托 即:回调函数,这个回调函数是一个没有返回值,带两个参数的方法,第一个参数是object,第二个但是是bool【这个bool参数表示 如果 WaitHandle 超时,则为 true;如果其终止,则为 false】 //第三个参数是:传递给委托的参数(传递给WaitOrTimerCallback这个委托的第一个参数) //第四个参数是:以毫秒为单位的超时(即:多少毫秒调用一次)值为0表示一直执行下去,值为-1表示则函数的超时间隔永远不过期,即不调用 //第五个参数:我不懂意思,一般情况下都是false吧 ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), new WaitOrTimerCallback((obj, b) => { //这里可以写逻辑判断,判断是否在某一时刻执行 Console.WriteLine("obj={0},tid={1},datetime={2},b={3}", obj, Thread.CurrentThread.ManagedThreadId, DateTime.Now, b); }), "helloword", 2000, false); //2000表示2000毫秒执行一次 Console.ReadKey(); }}
~
public class User{ public int Id { get; set; } public string Name { get; set; }}class Program{ static AutoResetEvent wait = new AutoResetEvent(false); static void Main(string[] args) { User user = new User() { Id = 1, Name = "Lily" }; ThreadPool.RegisterWaitForSingleObject(wait, new WaitOrTimerCallback(test11), user, 2000, false); Console.ReadKey(); } //这个方法每2000毫秒调用一次 private static void test11(object obj, bool timedOut) { User u = obj as User; Console.WriteLine(u.Id); }}
- Thread
- thread
- Thread
- Thread
- thread
- thread
- Thread
- Thread
- Thread
- Thread
- Thread
- Thread
- Thread
- Thread
- Thread
- Thread
- Thread
- Thread
- 云阅片 微云影像旗下个人医学影像助手
- 获取 ESXi 服务器 thumbprint
- 2017-11-13
- 20171113关于类模版和和其特化的格式问题
- 403 Forbidden问题
- Thread
- 装饰器
- 函数调用栈帧过程 图解
- 【Unity Shader入门练习】Bloom效果
- 二叉树的线索化及其迭代器
- 【量化小讲堂-Python&Pandas系列11】法码三因子选股模型,有多少人可以跑赢
- FPGA静态时序分析基本概念
- 南邮ctf逆向最后一题
- Android开发之基类Object官方文档翻译