zoukankan      html  css  js  c++  java
  • 第二节:深入剖析Thread的五大方法、数据槽、内存栅栏。

    一. Thread及其五大方法

       Thread是.Net最早的多线程处理方式,它出现在.Net1.0时代,虽然现在已逐渐被微软所抛弃,微软强烈推荐使用Task(后面章节介绍),但从多线程完整性的角度上来说,我们有必要了解下N年前多线程的是怎么处理的,以便体会.Net体系中多线程处理方式的进化。

      Thread中有五大方法,分别是:Start、Suspend、Resume、Intterupt、Abort

      ①.Start:开启线程

      ②.Suspend:暂停线程

      ③.Resume:恢复暂停的线程

      ④.Intterupt:中断线程(会抛异常,提示线程中断)

      ⑤.Abort:销毁线程

        这五大方法使用起来,也比较简单,下面贴一段代码,体会一下如何使用即可。

     

      在这里补充一下,在该系列中,很多测试代码中看到TestThread0、TestThread、TestThread2,分别对应无参、一个参数、两个参数的耗时方法,代码如下:

     1   /// <summary>
     2         /// 执行动作:耗时而已
     3         /// </summary>
     4         private void TestThread0()
     5         {
     6             Console.WriteLine("线程开始:当前线程的id为:{0},当前时间为:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
     7             long sum = 0;
     8             for (int i = 1; i < 999999999; i++)
     9             {
    10                 sum += i;
    11             }
    12             Console.WriteLine("线程结束:当前线程的id为::{0},当前时间为:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
    13         }
    14 
    15         /// <summary>
    16         /// 执行动作:耗时而已
    17         /// </summary>
    18         private void TestThread(string threadName)
    19         {
    20             Console.WriteLine("线程开始:线程名为:{2},当前线程的id为:{0},当前时间为:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName);
    21             long sum = 0;
    22             for (int i = 1; i < 999999999; i++)
    23             {
    24                 sum += i;
    25             }
    26             Console.WriteLine("线程结束:线程名为:{2},当前线程的id为::{0},当前时间为:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName);
    27         }
    28 
    29         /// <summary>
    30         /// 执行动作:耗时而已
    31         /// </summary>
    32         private void TestThread2(string threadName1, string threadName2)
    33         {
    34             Console.WriteLine("线程开始:线程名为:{2}和{3},当前线程的id为:{0},当前时间为:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName1, threadName2);
    35             long sum = 0;
    36             for (int i = 1; i < 999999999; i++)
    37             {
    38                 sum += i;
    39             }
    40             Console.WriteLine("线程结束:线程名为:{2}和{3},当前线程的id为::{0},当前时间为:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName1, threadName2);
    41         }
    View Code

    二. 从源码角度分析Thread类

    (1)  分析Thread类的源码,发现其构造函数有两类,分别是ThreadStart和ParameterizedThreadStart类,

     其中

      ①:ThreadStart类,是无参无返回值的委托。

      ②:ParameterizedThreadStart类,是有一个object类型参数但无返回值的委托.

    使用方法:

      ①:针对ThreadStart类, ThreadStart myTs = () => TestThread(name);  然后再把myTs传入Thread的构造函数中

      ②:针对ParameterizedThreadStart类,ParameterizedThreadStart myTs = o => this.TestThread(o.ToString());  然后再把myTs传入Thread的构造函数中

      注:该方式存在拆箱和装箱的转换问题,不建议这么使用

    通用写法

         Thread t = new Thread(() =>

        {

           Console.Write("333");

        });

        t.Start();

    无须考虑Thread的构造函数,也不需要考虑Start的时候传参,直接使用()=>{}的形式,解决一切问题。

    (二) 前台进程和后台进程(IsBackground属性)

      ①:前台进程,Thread默认为前台线程,程序关闭后,线程仍然继续,直到计算完为止

      ②:后台进程,将IsBackground属性设置为true,即为后台进程,主线程关闭,所有子线程无论运行完否,都马上关闭

    (三) 线程等待(Join方法)

      利用Join方法实现主线程等待子线程,当多个子线程进行等待的时候,只能通过for循环来实现

     下面贴一下这三块设计到的代码:

     1   private void button3_Click(object sender, EventArgs e)
     2         {
     3             Stopwatch watch = new Stopwatch();
     4             watch.Start();
     5             Console.WriteLine("-----------------Thread多线程  --------------------------");
     6             Console.WriteLine("----------------- button_Click 开始 主线程id为:{0}  --------------------------", Thread.CurrentThread.ManagedThreadId);
     7             List<Thread> threadList = new List<Thread>();
     8             for (int i = 0; i < 5; i++)
     9             {
    10                 string name = string.Format("button1_Click{0}", i);
    11 
    12                 #region 方式一
    13                 {
    14                     ThreadStart myTs = () => TestThread(name);
    15                     Thread myThread = new Thread(myTs);
    16                     //Thread默认为前台线程,程序关闭后,线程仍然继续,直到计算完为止
    17                     myThread.IsBackground = true;   //设置为后台线程,主程序关闭所有线程均关闭
    18                     myThread.Start();
    19 
    20                     threadList.Add(myThread);
    21                 }
    22 
    23                 #endregion
    24 
    25                 #region 方式二
    26                 //{
    27                 //    ParameterizedThreadStart myTs = o => this.TestThread(o.ToString());
    28                 //    //ParameterizedThreadStart myTs = (o) =>
    29                 //    //{
    30                 //    //    this.TestThread(o.ToString());
    31                 //    //};
    32                 //    Thread myThread = new Thread(myTs);
    33                 //    myThread.Start(name);
    34 
    35                 //    threadList.Add(myThread);
    36                 //}
    37 
    38                 #endregion
    39             }
    40 
    41             #region Thread线程等待
    42 
    43             //利用join方法进行线程等待
    44             foreach (Thread thread in threadList)
    45             {
    46                 thread.Join();
    47             }
    48             #endregion
    49 
    50             watch.Stop();
    51             Console.WriteLine("----------------- button_Click 结束 主线程id为:{0}  总耗时:{1}--------------------------", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds);
    52 
    53         }
    View Code

    (四). 扩展:Thread实现线程回调

     

    三. 数据槽-线程可见性

      背景:为了解决多线程竞用共享资源的问题,引入数据槽的概念,即将数据存放到线程的环境块中,使该数据只能单一线程访问.(属于线程空间上的开销)

      下面的三种方式是解决多线程竞用共享资源的通用方式:

      ①:AllocateNamedDataSlot命名槽位和AllocateDataSlot未命名槽位 解决线程竞用资源共享问题。

      (PS:在主线程上设置槽位,使该数据只能被主线程读取,其它线程无法访问)

       private void button10_Click(object sender, EventArgs e)
            {
                #region 01-AllocateNamedDataSlot命名槽位
                {
                    var d = Thread.AllocateNamedDataSlot("userName");
                    //在主线程上设置槽位,使该数据只能被主线程读取,其它线程无法访问
                    Thread.SetData(d, "ypf");
                    //声明一个子线程
                    var t1 = new Thread(() =>
                    {
                        Console.WriteLine("子线程中读取数据:{0}", Thread.GetData(d));
                    });
                    t1.Start();
    
                    //主线程中读取数据
                    Console.WriteLine("主线程中读取数据:{0}", Thread.GetData(d));
                }
    
                #endregion
    
                #region 02-AllocateDataSlot未命名槽位
                {
                    var d = Thread.AllocateDataSlot();
                    //在主线程上设置槽位,使该数据只能被主线程读取,其它线程无法访问
                    Thread.SetData(d, "ypf");
                    //声明一个子线程
                    var t1 = new Thread(() =>
                    {
                        Console.WriteLine("子线程中读取数据:{0}", Thread.GetData(d));
                    });
                    t1.Start();
    
                    //主线程中读取数据
                    Console.WriteLine("主线程中读取数据:{0}", Thread.GetData(d));
    
                }
                #endregion
    
            }

      ②:利用特性[ThreadStatic] 解决线程竞用资源共享问题

       (PS:在主线程中给ThreadStatic特性标注的变量赋值,则只有主线程能访问该变量)

      ③:利用ThreadLocal线程的本地存储, 解决线程竞用资源共享问题(线程可见性)

      (PS: 在主线程中声明ThreadLocal变量,并对其赋值,则只有主线程能访问该变量)

     

    四. 内存栅栏-线程共享资源

    背景:当多个线程共享一个变量的时候,在Release模式的优化下,子线程会将共享变量加载的cup Cache中,导致主线程不能使用该变量而无法运行。

    解决方案:

      ①:不要让多线程去操作同一个共享变量,从根本上解决这个问题。

      ②:利用MemoryBarrier方法进行处理,在此方法之前的内存写入都要及时从cpu cache中更新到 memory;在此方法之后的内存读取都要从memory中读取,而不是cpu cache。

      ③:利用VolatileRead/Write方法进行处理。

     1  private void button11_Click(object sender, EventArgs e)
     2         {
     3             #region 01-默认情况(Release模式主线程不能正常运行)
     4             //{
     5             //    var isStop = false;
     6             //    var t = new Thread(() =>
     7             //    {
     8             //        var isSuccess = false;
     9             //        while (!isStop)
    10             //        {
    11             //            isSuccess = !isSuccess;
    12             //        }
    13             //        Console.WriteLine("子线程执行成功");
    14             //    });
    15             //    t.Start();
    16 
    17             //    Thread.Sleep(1000);
    18             //    isStop = true;
    19 
    20             //    t.Join();
    21             //    Console.WriteLine("主线程执行结束");
    22             //}
    23             #endregion
    24 
    25             #region 02-MemoryBarrier解决共享变量(Release模式下可以正常运行)
    26             //{
    27             //    var isStop = false;
    28             //    var t = new Thread(() =>
    29             //    {
    30             //        var isSuccess = false;
    31             //        while (!isStop)
    32             //        {
    33             //            Thread.MemoryBarrier();
    34 
    35             //            isSuccess = !isSuccess;
    36             //        }
    37             //        Console.WriteLine("子线程执行成功");
    38             //    });
    39             //    t.Start();
    40 
    41             //    Thread.Sleep(1000);
    42             //    isStop = true;
    43 
    44             //    t.Join();
    45             //    Console.WriteLine("主线程执行结束");
    46             //}
    47             #endregion
    48 
    49             #region 03-VolatileRead解决共享变量(Release模式下可以正常运行)
    50             {
    51                 var isStop = 0;
    52                 var t = new Thread(() =>
    53                 {
    54                     var isSuccess = false;
    55                     while (isStop == 0)
    56                     {
    57                         Thread.VolatileRead(ref isStop);
    58 
    59                         isSuccess = !isSuccess;
    60                     }
    61                     Console.WriteLine("子线程执行成功");
    62                 });
    63                 t.Start();
    64 
    65                 Thread.Sleep(1000);
    66                 isStop = 1;
    67 
    68                 t.Join();
    69                 Console.WriteLine("主线程执行结束");
    70             }
    71             #endregion
    72 
    73 
    74         }
    View Code

  • 相关阅读:
    hdu 5936 2016ccpc 杭州
    bzoj 1218: [HNOI2003]激光炸弹
    bzoj 1296: [SCOI2009]粉刷匠
    桃子到底有多少
    计算x的n次方
    计算x的n次方
    菲波拉契数列
    菲波拉契数列
    八皇后(N皇后)问题算法程序(回溯法)
    八皇后(N皇后)问题算法程序(回溯法)
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/8135829.html
Copyright © 2011-2022 走看看