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);    }}



原创粉丝点击