第二节:深入剖析Thread的五大方法、数据槽、内存栅栏。

2024-01-02 00:32

本文主要是介绍第二节:深入剖析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         }

复制代码

 

二. 从源码角度分析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循环来实现

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

 

 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         }

这篇关于第二节:深入剖析Thread的五大方法、数据槽、内存栅栏。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/560905

相关文章

SQL Server配置管理器无法打开的四种解决方法

《SQLServer配置管理器无法打开的四种解决方法》本文总结了SQLServer配置管理器无法打开的四种解决方法,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录方法一:桌面图标进入方法二:运行窗口进入检查版本号对照表php方法三:查找文件路径方法四:检查 S

MyBatis-Plus 中 nested() 与 and() 方法详解(最佳实践场景)

《MyBatis-Plus中nested()与and()方法详解(最佳实践场景)》在MyBatis-Plus的条件构造器中,nested()和and()都是用于构建复杂查询条件的关键方法,但... 目录MyBATis-Plus 中nested()与and()方法详解一、核心区别对比二、方法详解1.and()

MySQL 删除数据详解(最新整理)

《MySQL删除数据详解(最新整理)》:本文主要介绍MySQL删除数据的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、前言二、mysql 中的三种删除方式1.DELETE语句✅ 基本语法: 示例:2.TRUNCATE语句✅ 基本语

golang中reflect包的常用方法

《golang中reflect包的常用方法》Go反射reflect包提供类型和值方法,用于获取类型信息、访问字段、调用方法等,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值... 目录reflect包方法总结类型 (Type) 方法值 (Value) 方法reflect包方法总结

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语