一. Thread及其五大方法

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

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

  ①.Start:开启线程

  ②.Suspend:暂停线程

  ③.Resume:恢复暂停的线程

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

  ⑤.Abort:销毁线程

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

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

   /// <summary>
/// 执行动作:耗时而已
/// </summary>
private void TestThread0()
{
Console.WriteLine("线程开始:当前线程的id为:{0},当前时间为:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
long sum = ;
for (int i = ; i < ; i++)
{
sum += i;
}
Console.WriteLine("线程结束:当前线程的id为::{0},当前时间为:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
} /// <summary>
/// 执行动作:耗时而已
/// </summary>
private void TestThread(string threadName)
{
Console.WriteLine("线程开始:线程名为:{2},当前线程的id为:{0},当前时间为:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName);
long sum = ;
for (int i = ; i < ; i++)
{
sum += i;
}
Console.WriteLine("线程结束:线程名为:{2},当前线程的id为::{0},当前时间为:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName);
} /// <summary>
/// 执行动作:耗时而已
/// </summary>
private void TestThread2(string threadName1, string threadName2)
{
Console.WriteLine("线程开始:线程名为:{2}和{3},当前线程的id为:{0},当前时间为:{1},", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName1, threadName2);
long sum = ;
for (int i = ; i < ; i++)
{
sum += i;
}
Console.WriteLine("线程结束:线程名为:{2}和{3},当前线程的id为::{0},当前时间为:{1}", System.Threading.Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"), threadName1, threadName2);
}

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

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

   private void button3_Click(object sender, EventArgs e)
{
Stopwatch watch = new Stopwatch();
watch.Start();
Console.WriteLine("-----------------Thread多线程 --------------------------");
Console.WriteLine("----------------- button_Click 开始 主线程id为:{0} --------------------------", Thread.CurrentThread.ManagedThreadId);
List<Thread> threadList = new List<Thread>();
for (int i = ; i < ; i++)
{
string name = string.Format("button1_Click{0}", i); #region 方式一
{
ThreadStart myTs = () => TestThread(name);
Thread myThread = new Thread(myTs);
//Thread默认为前台线程,程序关闭后,线程仍然继续,直到计算完为止
myThread.IsBackground = true; //设置为后台线程,主程序关闭所有线程均关闭
myThread.Start(); threadList.Add(myThread);
} #endregion #region 方式二
//{
// ParameterizedThreadStart myTs = o => this.TestThread(o.ToString());
// //ParameterizedThreadStart myTs = (o) =>
// //{
// // this.TestThread(o.ToString());
// //};
// Thread myThread = new Thread(myTs);
// myThread.Start(name); // threadList.Add(myThread);
//} #endregion
} #region Thread线程等待 //利用join方法进行线程等待
foreach (Thread thread in threadList)
{
thread.Join();
}
#endregion watch.Stop();
Console.WriteLine("----------------- button_Click 结束 主线程id为:{0} 总耗时:{1}--------------------------", Thread.CurrentThread.ManagedThreadId, watch.ElapsedMilliseconds); }

(四). 扩展: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方法进行处理。

  private void button11_Click(object sender, EventArgs e)
{
#region 01-默认情况(Release模式主线程不能正常运行)
//{
// var isStop = false;
// var t = new Thread(() =>
// {
// var isSuccess = false;
// while (!isStop)
// {
// isSuccess = !isSuccess;
// }
// Console.WriteLine("子线程执行成功");
// });
// t.Start(); // Thread.Sleep(1000);
// isStop = true; // t.Join();
// Console.WriteLine("主线程执行结束");
//}
#endregion #region 02-MemoryBarrier解决共享变量(Release模式下可以正常运行)
//{
// var isStop = false;
// var t = new Thread(() =>
// {
// var isSuccess = false;
// while (!isStop)
// {
// Thread.MemoryBarrier(); // isSuccess = !isSuccess;
// }
// Console.WriteLine("子线程执行成功");
// });
// t.Start(); // Thread.Sleep(1000);
// isStop = true; // t.Join();
// Console.WriteLine("主线程执行结束");
//}
#endregion #region 03-VolatileRead解决共享变量(Release模式下可以正常运行)
{
var isStop = ;
var t = new Thread(() =>
{
var isSuccess = false;
while (isStop == )
{
Thread.VolatileRead(ref isStop); isSuccess = !isSuccess;
}
Console.WriteLine("子线程执行成功");
});
t.Start(); Thread.Sleep();
isStop = ; t.Join();
Console.WriteLine("主线程执行结束");
}
#endregion }

第二节:深入剖析Thread的五大方法、数据槽、内存栅栏。的更多相关文章

  1. 第二节:numpy之数组切片、数据类型转换、随机数组

  2. 第8.6节 Python类中的__new__方法深入剖析:调用父类__new__方法参数的困惑

    上节<第8.5节 Python类中的__new__方法和构造方法__init__关系深入剖析:执行顺序及参数关系案例详解>通过案例详细分析了两个方法的执行顺序,不知大家是否注意到了,在上述 ...

  3. 第8.5节 Python类中的__new__方法和构造方法__init__关系深入剖析:执行顺序及参数关系案例详解

    上节介绍了__new__()方法这个比构造方法还重要的方法的语法,本节通过案例来详细剖析__new__()方法的细节以及它与构造方法之间的关系. 一.    案例说明 本节以圆Cir类为例来说明,为了 ...

  4. 学习javascript基础知识系列第二节 - this用法

    通过一段代码学习javascript基础知识系列 第二节 - this用法 this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascr ...

  5. java多线程基本概述(二)——Thread的一些方法

    在Thread类中有很多方法值得我们关注一下.下面选取几个进行范例: 1.1.isAlive()方法 java api 描述如下: public final boolean isAlive() Tes ...

  6. 源码讲解 node+mongodb 建站攻略(一期)第二节

    源码讲解 node+mongodb 建站攻略(一期)第二节 上一节,我们完成了模拟数据,这次我们来玩儿真正的数据库,mongodb. 代码http://www.imlwj.com/download/n ...

  7. 第二节:Web前端-ASP.NET之C#基础

    第二节:Web前端-ASP.NET之C#基础 学习ASP.NET,要掌握学习语言,控件等技能, <div style="text-align: center; line-height: ...

  8. 【精编重制版】JavaWeb 入门级项目实战 -- 文章发布系统 (第二节)

    说明 本教程是,原文章发布系统教程的精编重制版,会包含每一节的源码,以及修正之前的一些错误.因为之前的教程只做到了评论模块,很多地方还不完美,因此重制版会修复之前的一些谬误和阐述不清的地方,而且,后期 ...

  9. 多线程Thread类的方法

    创建多个线程的第一种方法 1.定义一个Thread类的子类,比如MyThread类 2.重写Thread的run方法,设置线程任务 3.创建Mythread类的对象 4.调用方法start(),开启新 ...

随机推荐

  1. 使用Java反射优化多个方法调用

    有段时间没来写博客了,心里一直念叨空了来,今天有时间来记录一篇.前段时间领导提出优化部分系统模块,根据业务要求系统中有很多产品,产品下面有N个指标,一个指标就对应一个方法,所以系统代码中就是这样一个情 ...

  2. java格式化

    http://tool.oschina.net/apidocs/apidoc?api=jdk-zh https://www.jianshu.com/p/c8f16cab35e1# 参考官方的 api说 ...

  3. KindEditor富文本编辑器使用

    我的博客本来打算使用layui的富文本编辑器,但是出了一个问题,无法获取编辑器内容,我参考官方文档,获取内容也就那几个方法而已,但是引入进去后始终获取的值为空,百度和bing都试过了,但是始终还是获取 ...

  4. logrotate日志处理

    介绍 logrotate旨在简化生成大量日志文件的系统的管理.它允许日志文件的自动轮换.压缩.删除和邮件.每个日志文件可以每天.每周.每月处理,也可以在它变得太大时处理.通常,logrotate作为每 ...

  5. L1-8 矩阵A乘以B (15 分)

    给定两个矩阵A和B,要求你计算它们的乘积矩阵AB.需要注意的是,只有规模匹配的矩阵才可以相乘.即若A有R​a​​行.C​a​​列,B有R​b​​行.C​b​​列,则只有C​a​​与R​b​​相等时,两 ...

  6. Linux下安装 Python3

    前言 Linux下大部分系统默认自带python2.x的版本,最常见的是python2.6或python2.7版本,默认的python被系统很多程序所依赖,比如centos下的yum就是python2 ...

  7. Babel插件开发入门指南

    文章概览 主要包括:Babel如何进行转码.插件编写的入门基础.实例讲解如何编写插件. 阅读本文前,需要读者对Babel插件如何使用.配置有一定了解,可以参考笔者之前的文章. 本文所有例子可以在 笔者 ...

  8. Python进阶3---python类型注解、functools

    函数定义的弊端 函数注解Function Annotations 业务应用 inspect模块 #示例 import inspect def add(x,y:int,*args,**kwargs) - ...

  9. 使用Node.js搭建数据爬虫crawler

    0. 通用爬虫框架包括: (1) 将爬取url加入队列,并获取指定url的前端资源(crawler爬虫框架主要使用Crawler类进行抓取网页) (2)解析前端资源,获取指定所需字段的值,即获取有价值 ...

  10. Lodop简答问答大全

    其他相关简短问答:Lodop简短问答客户反馈篇 及排查步骤 及注册相关,Lodop某个电脑打印内容大小有问题,LODOP超文本简短问答和相关内容,LODOP.C-Lodop简短排查语句.Lodop.c ...