Thread

一:空间上的开销
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();
    }
}

 

前台线程 与 后台线程的区别

前台线程的特点:应用程序必须运行完所有的前台线程才可以退出;

后台线程的特点:应用程序可以不考虑其是否已经运行完毕而直接退出,所有的后台线程在应用程序退出时都会自动结束。

一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序。

线程创建时不做设置默认是前台线程。即线程属性IsBackground=false

ThreadPool

 

线程池的作用:

 

线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。

如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。

线程池中的线程都是后台线程:也就是说,其IsBackground属性是true; 这意味着,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);
    }
}

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值