一. 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 }
二. 从源码角度分析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 }
(四). 扩展: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 }