语言C#多线程和线程池[转]

1、概念

1. 怎样是前者工程化

自有前端工程师这些称谓以来,前端的迈入可谓是日新月异。相比已经特别干练的其他领域,前端虽是后起之秀,但其强行生长是任何领域不可以比的。固然前端技术急迅提升,可是前端全部的工程生态并不曾一并跟进。近来大部分的前端团队依旧使用分外原始的“切图(FE)->套模板(RD)”的付出情势,那种格局下的前端开发虽说不是刀耕火种的原来状态,不过成效很是低下。

前者的工程化问题与传统的软件工程即便有所不同,可是面临的问题是均等的。大家首先想起一下价值观的软件开发流程模型:
语言 1

上图中的运行和维护并不是串行关系,也毫不相对的相互关系。维护贯穿从编码到运行的一体流程。

假若说总括机科学要化解的是系统的某部具体问题,或者更通俗点说是面向编码的,那么工程化要解决的是何许加强整个系统生产成效。所以,与其说软件工程是一门科学,不如说它更偏向于管经济学和方法论。

软件工程是个很普遍的话题,每个人都有温馨的精晓。以上是作者个人的通晓,仅供参考。

切实到前者工程化,面临的题目是哪些提升编码->测试->维护等级的生育功效。

恐怕会有人觉得应当包括要求分析和设计阶段,上图呈现的软件开发模型中,这六个级次实际到前端开发领域,更适用的名号应该是法力需求分析和UI设计,分别由产品主任和UI工程师完成。至于API需求分析和API设计,应该包括在编码阶段。

  1.0 线程的和经过的涉及以及优缺点**

2. 前端工程化面临的题目

要解决前端工程化的题材,可以从三个角度动手:开发和布局。

从支付角度,要化解的题目包括:

  1. 增长支付生产功用;
  2. 降落维护难度。

这六个问题的缓解方案有两点:

  1. 制定开发规范,提升组织协作能力;
  2. 分治。软件工程中有个很关键的定义叫做模块化开发其中央思想就是分治。

从布局角度,要化解的题目重倘若资源管理,包括:

  1. 代码审查;
  2. 缩减打包;
  3. 增量更新;
  4. 单元测试;

要缓解上述问题,需要引入构建/编译阶段。

  windows系统是一个多线程的操作系统。一个顺序至少有一个经过,一个经过至少有一个线程。进程是线程的容器,一个C#客户端程序起首于一个独立的线程,CLR(公共语言运行库)为该过程成立了一个线程,该线程称为主线程。例如当大家创设一个C#控制台程序,程序的进口是Main()函数,Main()函数是始于一个主线程的。它的效率紧如若暴发新的线程和推行顺序。C#是一门帮忙多线程的编程语言,通过Thread类创造子线程,引入using
System.Threading命名空间。 

2.1 开发规范

开发规范的目标是统一团队成员的编码规范,便于团队协作和代码维护。开发规范没有统一的正规,每个集体可以制造友好的一套规范系列。

值得一提的是JavaScript的开销规范,尤其是在ES2015进一步普及的范围下,保持出色的编码风格是可怜必要的。笔者推荐Airbnb的eslint规范。

多线程的长处 

2.2 模块/组件化开发

1
2
1、 多线程可以提高CPU的利用率,因为当一个线程处于等待状态的时候,CPU会去执行另外的线程
2、 提高了CPU的利用率,就可以直接提高程序的整体执行速度
2.2.1 模块仍然组件?

成千上万人会搅乱模块化开发和组件化开发。但是严俊来讲,组件(component)和模块(module)应该是五个例外的概念。两者的区分紧要在颗粒度下边。《Documenting
Software Architectures》一书中对此component和module的诠释如下:

A module tends to refer first and foremost to a design-time entity.
… information hiding as the criterion for allocating responsibility
to a module.
A component tends to refer to a runtime entity. … The emphasis is
clearly on the finished product and not on the design considerations
that went into it.

In short, a module suggests encapsulation properties, with less
emphasis on the delivery medium and what goest on at runtime. Not so
with components. A delivered binary maintains its “separateness”
throughout execution. A component suggests independently deployed
units of software with no visibility into the development process.

简单易行讲,module侧重的是对性能的卷入,重心是在设计和开发阶段,不关注runtime的逻辑。module是一个白盒;而component是一个足以单独布置的软件单元,面向的是runtime,侧重于产品的功能性。component是一个黑盒,内部的逻辑是不可见的。

用深切浅出的话讲,模块可以明白为零件,比如轮胎上的螺丝钉钉;而组件则是皮带,是富有某项完整意义的一个完整。具体到前者领域,一个button是一个模块,一个席卷多少个button的nav是一个组件。

模块和组件的争辨由来已久,甚至某些编程语言对两岸的落实都模糊不清。前端领域也是这样,使用过bower的同行知道bower安装的第三方依赖目录是bower_component;而npm安装的目录是node_modules。也没必要为了那一个争得头破血流,一个团队只要统一思想,保证支付效用就足以了。至于是命名为module依然component都无所谓。

作者个人倾向组件黑盒、模块白盒这种思维。

多线程的弱项:

2.2.2 模块/组件化开发的必要性

乘机web应用范围进一步大,模块/组件化开发的急需就体现愈发迫切。模块/组件化开发的主题思想是分治,首要针对的是付出和维护阶段。

有关组件化开发的座谈和进行,业界有很多同行做了那几个详细的介绍,本文的重大并非关注组件化开发的事无巨细方案,便不再赘言了。笔者采访了有的资料可供参考:

  1. Web应用的组件化开发;
  2. 前端组件化开发执行;
  3. 大规模的前端组件化与模块化。

 

3. 构建&编译

当心地讲,构建(build)和编译(compile)是一点一滴不等同的五个概念。两者的颗粒度不同,compile面对的是单文件的编译,build是起家在compile的底子上,对整个文本举行编译。在许多Java
IDE中还有此外一个概念:make。make也是创立在compile的基础上,不过只会编译有改观的公文,以增强生产效用。本文不啄磨build、compile、make的深层运行机制,下文所述的前段工程化中构建&编译阶段简称为构建阶段。

1
2
3
1、线程开的越多,内存占用越大
2、协调和管理代码的难度加大,需要CPU时间跟踪线程
3、线程之间对资源的共享可能会产生可不遇知的问题

3.1 构建在前端工程中的角色

在座谈现实怎么着协会构建任务从前,我们首先追究一下在全部前端工程序列中,构建阶段扮演的是何许角色。

首先,我们看看近日这一个时间点(2016年),一个典型的web前后端协作形式是怎么着的。请看下图:
语言 2

上图是一个相比早熟的光景端协作序列。当然,目前出于Node.js的盛行起来普及大前端的概念,稍后会讲述。

自Node.js问世以来,前端圈子向来传出着一个词:颠覆。前端工程师要凭借Node.js颠覆以往的web开发格局,简单说就是用Node.js取代php、ruby、python等语言搭建web
server,在这一个颠覆运动中,JavaScript是前者工程师的自信心源泉。我们不钻探Node.js与php们的相比,只在倾向这一个角度来讲,大前端这一个主旋律吸引越来越多的前端工程师。

实则大前端也能够理解为全栈工程师,全栈的概念与编程语言没有相关性,核心的竞争力是对全部web产品从前到后的敞亮和摆布。

那么在大前端格局下,构建又是扮演什么角色吧?请看下图:
语言 3

大前端系列下,前端开发人士控制着Node.js搭建的web
server层。与上文提到的健康前端开发连串下相比,省略了mock
server的角色,不过构建在大前端类别下的效能并没有发生变动。也就是说,不论是大前端仍旧“小”前端,构建阶段在二种形式下的效能完全一致,构建的职能就是对静态资源以及模板举办处理,换句话说:构建的中央是资源管理

 

3.2 资源管理要做什么?

前者的资源得以分成静态资源和模板。模板对静态资源是援引关系,两者相辅相成,构建过程中需要对二种资源利用不同的构建政策。

当前还是有多数店家将模板交由后端开发人士控制,前端人士写好demo交给后端程序员“套模板”。这种协作情势功用是非凡低的,模板层交由前端开发人士各负其责可以很大程度上增强工作功效。

     1.1 前台线程和后台线程

3.2.1 静态资源构建政策

静态资源包括js、css、图片等文件,近日乘机有些新专业和css预编译器的普及,通常开发阶段的静态资源是:

  1. es6/7正经的文书;
  2. less/sass等文件(具体看团队技术选型);
  3. [可选]独自的小图标,在构建阶段拔取工具处理成spirit图片。

构建阶段在处理那么些静态文件时,基本的效果应包括:

  1. es6/7转译,比如babel;
  2. 将less/sass编译成css;
  3. spirit图片生成;

以上关联的多少个职能可以说是为了弥补浏览器自身功能的缺点,也可以领会为面向语言本身的,我们得以将这个成效统称为预编译。

除此之外语言本身,静态资源的构建处理还索要考虑web应用的特性因素。比如开发阶段使用组件化开发格局,每个组件有单独的js/css/图片等公事,如若不做处理每个文件独立上线的话,无疑会增多http请求的数据,从而影响web应用的属性表现。针对这样的问题,构建阶段需要包括以下职能:

  1. 借助于打包。分析文件看重关系,将联袂依赖的的公文打包在联名,裁减http请求数量;
  2. 资源嵌入。比如小于10KB的图片编译为base64格式嵌入文档,裁减一遍http请求;
  3. 文本缩小。减小文件体积;
  4. hash指纹。通过给文件名参预hash指纹,以应对浏览器缓存引起的静态资源改进问题;
  5. 代码审查。避免上线文件的低级错误;

如上多少个效益除了压缩是完全自动化的,其他两个效能都需要人工的部署。比如为了提升首屏渲染性能,开发人士在开发阶段需要尽量减弱同步依赖文件的多寡。

上述提到的具备功用能够知道为工具层面的构建功效。

如上提到的构建功能只是构建工具的基本效率。假诺停留在这些等级,那么也好不容易个合格的构建工具了,但也单独停留在工具层面。比较方今较流行的局部构建产品,比如fis,它有着以上所得的编译功效,同时提供了有的建制以增长开发阶段的生育效能。包括:

  1. 文本监听。配合动态构建、浏览器自动刷新等功用,提升支付功能;
  2. mock
    server。并非所有前端团队都是大前端(事实上很少团队是大前端),即使在大前端体系下,mock
    server的留存也是很有必要的;

俺们也得以将下面提到的效应明白为平台层面的构建效率。

   
 
C#中的线程分为前台线程和后台线程,线程创设时不做设置默认是前台线程。即线程属性IsBackground=false。

3.2.2 模板的构建政策

模板与静态资源是容器-模块关系。模板直接引用静态资源,经过构建后,静态资源的改动有以下几点:

  1. url改变。开发条件与线上环境的url肯定是不同的,不同品类的资源依然按照项目标CDN策略放在不同的服务器上;
  2. 文件名转移。静态资源通过构建之后,文件名被抬高hash指纹,内容的改观导致hash指纹的改观。

实则url包括文件名的变更,之所以将二者分别论述是为着让读者区分CDN与构建对资源的不同影响。

对此模板的构建主题是在静态资源url和文书名改成后,同步革新模板中资源的引用地址

前天敢于论调是退出模板的倚重,html由客户端模板引擎渲染,简单说就是文档内容由JavaScript生成,服务端模板只提供一个空壳子和基础的静态资源引用。这种模式更加常见,一些较成熟的框架也使得了这多少个格局的前行,比如React、Vue等。但当下大部分web产品为了增强首屏的特性表现,仍旧鞭长莫及脱离对服务端渲染的借助。所以对模板的构建处理依然很有必要性。

具体的构建政策依照每个团队的情状有所差别,比如有些团队中模板由后端工程师负责,这种格局下fis的资源映射表机制是相当好的缓解方案。本文不商讨现实的构建政策,后续随笔会详细描述。

模板的构建是工具层面的功用。

Thread.IsBackground = false;//false:设置为前台线程,系统默认为前台线程。
3.2.3 小结

构建可以分成工具层面和平台层面的效用:

  • 工具层面
  1. 预编译,包括es6/7语法转译、css预编译器处理、spirit图片生成;
  2. 借助打包;
  3. 资源嵌入;
  4. 文件缩小;
  5. hash指纹;
  6. 代码审查;
  7. 模板构建。
  • 阳台层面
  1. 文本监听,动态编译;
  2. mock server。

 区别以及如何使用:

4. 总结

一个整机的前端工程体系应该包括:

  1. 合并的开支规范;
  2. 组件化开发;
  3. 构建流程。

支付规范和组件化开发面向的开发阶段,核心是增高协会合作能力,提升开发效能并降低维护成本。

构建工具和平台解决了web产品一多样的工程问题,目的在于增进web产品的性能表现,提高开发效用。

随着Node.js的流行,对于前端的概念越来越广泛,在全方位web开发体系中。前端工程师的角色更是首要。本文论述的前端工程序列没有涉嫌Node.js这一层面,当一个集体步入大前端时代,前端的概念已经不仅仅是“前端”了,我想Web工程师这些名号更适于一些。

事先跟一位前端架构师研商构建中对此模块化的拍卖时,他涉嫌一个很风趣的视角:所谓的回落打包等为了性能做出的构建,其实是受限于客户端本身。试想,假若将来的浏览器帮忙周边出现请求、网络延迟小到可有可无,我们还亟需减小打包吗?

诚然,任何架构也好,策略能够,都是对眼前的一种缓解方案,并不是一条条铁律。脱离了一代,任何技术研商都没有意义。

学习前端的同桌们,欢迎参与前端学习互换群

前端学习沟通QQ群:461593224

    这三头的区分就是:应用程序必须运行完所有的前台线程才足以脱离;而对此后台线程,应用程序则足以不考虑其是否已经运行完毕而一贯退出,所有的后台线程在应用程序退出时都会自动终止。一般后台线程用于拍卖时间较短的任务,如在一个Web服务器中可以采取后台线程来处理客户端发过来的伏乞信息。而前台线程一般用来拍卖需要长日子等待的天职,如在Web服务器中的监听客户端请求的次序。

线程是寄托在过程上的,进程都终止了,线程也就流失了!

若果有一个前台线程未脱离,进程就不会终止!即说的就是程序不会倒闭!(即在资源管理器中得以看来进程未竣工。)

     1.3 多线程的创制

   
下面的代码创立了一个子线程,作为程序的入口mian()函数所在的线程即为主线程,我们通过Thread类来创制子线程,Thread类有 ThreadStart 和
ParameterizedThreadStart类型的嘱托参数,我们也可以一向写方法的名字。线程执行的措施可以传递参数(可选),参数的项目为object,写在Start()里。

语言 4

class Program
 {
        //我们的控制台程序入口是main函数。它所在的线程即是主线程
        static void Main(string[] args)     
        {
            Thread thread = new Thread(ThreadMethod);     //执行的必须是无返回值的方法
            thread.Name = "子线程";
            //thread.Start("王建");                       //在此方法内传递参数,类型为object,发送和接收涉及到拆装箱操作
            thread.Start(); 
            Console.ReadKey();
        }

        public static void ThreadMethod(object parameter) //方法内可以有参数,也可以没有参数
        {
            Console.WriteLine("{0}开始执行。", Thread.CurrentThread.Name);
        }
  }

语言 5

率先应用new
Thread()创立出新的线程,然后调用Start方法使得线程进入就绪状态,得到系统资源后就实施,在推行进程中恐怕有等待、休眠、死亡和堵塞四种状态。正常履行完毕时间片后重临到就绪状态。假设调用Suspend方法会进去等待状态,调用Sleep或者境遇进程同步使用的锁机制而休眠等待。具体过程如下图所示:

语言 6

2、线程的基本操作

线程和另外常见的类一样,有着广大特性和情势,参考下表:

语言 7

2.1 线程的连带属性

我们可以经过地点表中的习性获取线程的有些连锁信息,下边是代码显示和输出结果:

语言 8

static void Main(string[] args)     
        {
            Thread thread = new Thread(ThreadMethod);     //执行的必须是无返回值的方法
            thread.Name = "子线程"; 
            thread.Start();
            StringBuilder threadInfo = new StringBuilder();
            threadInfo.Append(" 线程当前的执行状态: " + thread.IsAlive);
            threadInfo.Append("\n 线程当前的名字: " + thread.Name);
            threadInfo.Append("\n 线程当前的优先级: " + thread.Priority);
            threadInfo.Append("\n 线程当前的状态: " + thread.ThreadState);
            Console.Write(threadInfo);
            Console.ReadKey();
        }

        public static void ThreadMethod(object parameter)  
        {
            Console.WriteLine("{0}开始执行。", Thread.CurrentThread.Name);
        }

语言 9

 输输出结果: 语言 10

2.2 线程的相关操作

  2.2.1 Abort()方法

     Abort()方法用来终止线程,调用此方法强制截止正在实践的线程,它会抛出一个ThreadAbortException分外从而致使目的线程的平息。下边代码演示:

     

语言 11

static void Main(string[] args)     
        {
            Thread thread = new Thread(ThreadMethod);     //执行的必须是无返回值的方法 
            thread.Name = "小A";
            thread.Start();  
            Console.ReadKey();
        }

        public static void ThreadMethod(object parameter)  
        {
            Console.WriteLine("我是:{0},我要终止了", Thread.CurrentThread.Name);
            //开始终止线程
            Thread.CurrentThread.Abort();
            //下面的代码不会执行
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
            }
        }

语言 12

履行结果:和大家想像的相同,下面的轮回没有被实施语言 13

 

  2.2.2 ResetAbort()方法

  
   Abort方法可以经过跑出ThreadAbortException非凡中止线程,而拔取ResetAbort方法可以撤废中止线程的操作,下边通过代码演示使用 ResetAbort方法。

语言 14

     static void Main(string[] args)     
        {
            Thread thread = new Thread(ThreadMethod);     //执行的必须是无返回值的方法 
            thread.Name = "小A";
            thread.Start();  
            Console.ReadKey();
        }

        public static void ThreadMethod(object parameter)  
        {
            try
            {
                Console.WriteLine("我是:{0},我要终止了", Thread.CurrentThread.Name); 
         //开始终止线程
                Thread.CurrentThread.Abort();
            }
            catch(ThreadAbortException ex)
            {
                Console.WriteLine("我是:{0},我又恢复了", Thread.CurrentThread.Name);
         //恢复被终止的线程
                Thread.ResetAbort();
            }
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
            }
        }

语言 15

举办结果:语言 16

  2.2.3 Sleep()方法 

      
Sleep()方法调已阻塞线程,是当下线程进入休眠状态,在蛰伏过程中占有系统内存可是不占用系统时间,当休眠期之后,继续执行,注解如下:
 

        public static void Sleep(TimeSpan timeout);          //时间段
        public static void Sleep(int millisecondsTimeout);   //毫秒数

  实例代码: 

语言 17

       static void Main(string[] args)
        {
            Thread threadA = new Thread(ThreadMethod);     //执行的必须是无返回值的方法 
            threadA.Name = "小A";
            threadA.Start();
            Console.ReadKey();
        } 
        public static void ThreadMethod(object parameter)  
        { 
            for (int i = 0; i < 10; i++)
            { 
                Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
                Thread.Sleep(300);         //休眠300毫秒              
            }
        }

语言 18

将方面的代码执行未来,可以清楚的观察每趟循环之间离开300毫秒的时日。

      2.2.4 join()方法

    
 Join方法紧若是用来阻塞调用线程,直到某个线程终止或通过了点名时间截至。官方的解释相比单调,通俗的说就是开创一个子线程,给它加了这个情势,此外线程就会搁浅实施,直到这一个线程执行完截至才去执行(包括主线程)。她的法子声明如下:

 public void Join();
 public bool Join(int millisecondsTimeout);    //毫秒数
 public bool Join(TimeSpan timeout);       //时间段

为了注解方面所说的,我们首先看一段代码:  

语言 19

static void Main(string[] args)
        {
            Thread threadA = new Thread(ThreadMethod);     //执行的必须是无返回值的方法 
            threadA.Name = "小A";
            Thread threadB = new Thread(ThreadMethod);     //执行的必须是无返回值的方法  
            threadB.Name = "小B";
            threadA.Start();
       //threadA.Join();      
            threadB.Start();
       //threadB.Join();

            for (int i = 0; i < 10; i++)
            { 
                Console.WriteLine("我是:主线程,我循环{1}次", Thread.CurrentThread.Name, i);
                Thread.Sleep(300);          //休眠300毫秒                                                
            }
            Console.ReadKey();
        } 
        public static void ThreadMethod(object parameter)  
        { 
            for (int i = 0; i < 10; i++)
            { 
                Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name,i);
                Thread.Sleep(300);         //休眠300毫秒              
            }
        }

语言 20

 

因为线程之间的推行是任意的,所有执行结果和大家想象的一致,杂乱无章!可是表达他俩是还要举行的。语言 21

     现在咱们把代码中的
 ThreadA.join()方法注释裁撤,首先程序中有两个线程,ThreadA、ThreadB和主线程,首先主线程先阻塞,然后线程ThreadB阻塞,ThreadA先进行,执行完毕之后ThreadB接着执行,最终才是主线程执行。

看实践结果:

语言 22

        2.2.5 Suspent()和Resume()方法

       其实在C# 2.0之后,
Suspent()和Resume()方法已经过时了。suspend()方法容易爆发死锁。调用suspend()的时候,目的线程会停下来,但却如故拥有在那往日得到的锁定。此时,其他任何线程都无法访问锁定的资源,除非被”挂起”的线程恢复生机运行。对任何线程来说,如若它们想过来目的线程,同时又意欲动用其余一个锁定的资源,就会促成死锁。所以不应该运用suspend()。

 

语言 23

     static void Main(string[] args)
        {
            Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "小A";  
            threadA.Start();  
            Thread.Sleep(3000);         //休眠3000毫秒      
            threadA.Resume();           //继续执行已经挂起的线程
            Console.ReadKey();
        }
        public static void ThreadMethod(object parameter)
        {
            Thread.CurrentThread.Suspend();  //挂起当前线程
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); 
            }
        }

语言 24

 

       执行上边的代码。窗口并不曾立时执行
ThreadMethod方法输出循环数字,而是等待了三分钟之后才输出,因为线程开端履行的时候实施了Suspend()方法挂起。然后主线程休眠了3分钟将来又经过Resume()方法复苏了线程threadA。

    2.2.6 线程的先行级

  假设在应用程序中有三个线程在运作,但一些线程比另一部分线程首要,这种气象下可以在一个经过中为不同的线程指定不同的先期级。线程的先行级可以通过Thread类Priority属性设置,Priority属性是一个ThreadPriority型枚举,列举了5个先行等级:Above诺玛(Norma)l、BelowNormal、Highest、Lowest、诺玛(Norma)l。公共语言运行库默认是Normal类型的。见下图:

语言 25

直白上代码来看效用:

语言 26

语言 27

static void Main(string[] args)
        {                
            Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "A";
            Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadB.Name = "B";
            threadA.Priority = ThreadPriority.Highest;
            threadB.Priority = ThreadPriority.BelowNormal;
            threadB.Start();
            threadA.Start();
            Thread.CurrentThread.Name = "C";
            ThreadMethod(new object());
            Console.ReadKey();
        }
        public static void ThreadMethod(object parameter)
        {
            for (int i = 0; i < 500; i++)
            { 
                Console.Write(Thread.CurrentThread.Name); 
            }
        }

语言 28

实施结果:

语言 29

地方的代码中有多少个线程,threadA,threadB和主线程,threadA优先级最高,threadB优先级最低。那点从运行结果中也得以看来,线程B
偶尔会见世在主线程和线程A前边。当有两个线程同时地处可实施情形,系统优先执行优先级较高的线程,但这只象征优先级较高的线程占有更多的CPU时间,并不意味一定要先举行完优先级较高的线程,才会履行优先级较低的线程。

预先级越高意味着CPU分配给该线程的时光片越多,执行时间就多

事先级越低表示CPU分配给该线程的年月片越少,执行时间就少

   3、线程同步

  什么是线程安全:

  线程安全是指在当一个线程访问该类的某部数据时,举办珍重,其他线程不能够进行访问直到该线程读取完,其他线程才可采取。不会现出数量不一样或者数额污染。

   线程有可能和另外线程共享一些资源,比如,内存,文件,数据库等。当两个线程同时读写同一份共享资源的时候,可能会滋生顶牛。这时候,我们需要引入线程“同步”机制,即诸君线程之间要有个先来后到,不可能一窝蜂挤上去抢作一团。线程同步的实在意思和字面意思恰好相反。线程同步的真正意思,其实是“排队”:多少个线程之间要排队,一个一个对共享资源举行操作,而不是同时展开操作。

干什么要促成联机啊,下面的例证大家拿出名的单例格局以来吧。看代码

语言 30

public class Singleton
    {
        private static Singleton instance; 
        private Singleton()   //私有函数,防止实例
        {

        } 
        public static Singleton GetInstance()
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }

语言 31

     
 单例格局就是保险在漫天应用程序的生命周期中,在任何时刻,被指定的类只有一个实例,并为客户程序提供一个到手该实例的全局访问点。但地点代码有一个分明的问题,这就是假诺多少个线程同时去取得这么些目的实例,那。。。。。。。。

俺们队代码举办改动:

语言 32

public class Singleton
{
       private static Singleton instance;
       private static object obj=new object(); 
       private Singleton()        //私有化构造函数
       {

       } 
       public static Singleton GetInstance()
       {
               if(instance==null)
               {
                      lock(obj)      //通过Lock关键字实现同步
                      {
                             if(instance==null)
                             {
                                     instance=new Singleton();
                             }
                      }
               }
               return instance;
       }
}

语言 33

经过改动后的代码。加了一个
lock(obj)代码块。这样就可知落实协同了,尽管不是很精通的话,大家看前边继续上课~

  3.0 使用Lock关键字贯彻线程同步 

  首先创造五个线程,五个线程执行同一个办法,参考下面的代码:

语言 34

static void Main(string[] args)
        {
            Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadB.Name = "生旭鹏";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        public static void ThreadMethod(object parameter)
        { 
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i);
                Thread.Sleep(300);
            }
        }

语言 35

实施结果:

语言 36

 

透过下面的推行结果,可以很了然的来看,五个线程是在同时履行ThreadMethod这一个主意,这明确不切合我们线程同步的要求。我们对代码举行修改如下:

语言 37

语言 38

static void Main(string[] args)
        {
            Program pro = new Program();
            Thread threadA = new Thread(pro.ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(pro.ThreadMethod); //执行的必须是无返回值的方法 
            threadB.Name = "生旭鹏";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        public void ThreadMethod(object parameter)
        {
            lock (this)             //添加lock关键字
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i);
                    Thread.Sleep(300);
                }
            } 
        }

语言 39

举办结果:

语言 40

大家经过添加了 lock(this)
{…}代码,查看执行结果实现了我们想要的线程同步要求。不过我们知晓this表示最近类实例的自身,那么有如此一种状态,我们把需要拜访的法门所在的品类举办六个实例A和B,线程A访问实例A的措施ThreadMethod,线程B访问实例B的措施ThreadMethod,这样的话还是可以够达成线程同步的需求吗。

语言 41

语言 42

static void Main(string[] args)
        {
            Program pro1 = new Program();                    
            Program pro2 = new Program();                   
            Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法 
            threadB.Name = "生旭鹏";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        public void ThreadMethod(object parameter)
        {
            lock (this)
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i);
                    Thread.Sleep(300);
                }
            }
        }

语言 43

推行结果:

语言 44

俺们会发觉,线程又从不兑现同步了!lock(this)对于那种情景是不行的!所以需要我们对代码举行改动!修改后的代码如下: 

语言 45

语言 46

private static object obj = new object();
        static void Main(string[] args)
        {
            Program pro1 = new Program();                    
            Program pro2 = new Program();                   
            Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "王文建";
            Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法 
            threadB.Name = "生旭鹏";
            threadA.Start();
            threadB.Start();
            Console.ReadKey();
        }
        public void ThreadMethod(object parameter)
        {
            lock (obj)
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i);
                    Thread.Sleep(300);
                }
            }
        }

语言 47

通过查阅执行结果。会发现代码实现了俺们的要求。那么 lock(this)
和lock(Obj)有如何区别吗? 

lock(this) 锁定 当前实例对象,如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。所有不推荐使用。 
lock(typeof(Model))锁定的是model类的所有实例。 
lock(obj)锁定的对象是全局的私有化静态变量。外部无法对该变量进行访问。 
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。 
所以,lock的结果好不好,还是关键看锁的谁,如果外边能对这个谁进行修改,lock就失去了作用。所以一般情况下,使用私有的、静态的并且是只读的对象。

总结:

1、lock的是必须是引用类型的对象,string类型除外。

2、lock推荐的做法是行使静态的、只读的、私有的对象。

3、保证lock的目的在外表不可以修改才有意义,假若lock的靶子在表面改变了,对另外线程就会通行,失去了lock的意思。

*     无法锁定字符串,锁定字符串尤其危险,因为字符串被集体语言运行库
(CLR)“暂留”。
这意味整个程序中任何给定字符串都唯有一个实例,就是这同一个对象表示了所有运行的采纳程序域的有所线程中的该文件。由此,只要在应用程序进程中的任何地方处具有相同内容的字符串上停放了锁,就将锁定应用程序中该字符串的兼具实例。平日,最好避免锁定
public
类型或锁定不受应用程序控制的目的实例。例如,尽管该实例可以被公开访问,则
lock(this)
可能会有问题,因为不受控制的代码也恐怕会锁定该对象。这恐怕引致死锁,即五个或更六个线程等待释放同一对象。出于同样的因由,锁定公共数据类型(相比较于对象)也恐怕引致问题。而且lock(this)只对当前目标有效,假使四个目的之间就达不到一块的法力。lock(typeof(Class))与锁定字符串一样,范围太广了。*

  3.1 使用Monitor类实现线程同步      

     
Lock关键字是Monitor的一种替换用法,lock在IL代码中会被翻译成Monitor. 

     lock(obj)

              {
                 //代码段
             } 
    就一样 
    Monitor.Enter(obj); 
                //代码段
    Monitor.Exit(obj);  

           Monitor的常用属性和情势:

    Enter(Object) 在指定对象上得到排他锁。

    Exit(Object) 释放指定对象上的排他锁。 

 

    Pulse 布告等待队列中的线程锁定目标处境的更动。

    PulseAll 通告所有的等候线程对象情状的转移。

    TryEnter(Object) 试图获取指定对象的排他锁。

    TryEnter(Object,
Boolean)
 尝试拿到指定对象上的排他锁,并活动安装一个值,提示是否得到了该锁。

    Wait(Object) 释放对象上的锁并阻止当前线程,直到它再也赢得该锁。

     
常用的主意有五个,Monitor.Enter(object)方法是得到锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个办法,在拔取过程中为了避免获取锁之后因为非凡,致锁不能自由,所以需要在try{}
catch(){}之后的finally{}结构体中释放锁(Monitor.Exit())。

Enter(Object)的用法很简短,看代码 

语言 48

     static void Main(string[] args)
        {                
            Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "A";
            Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadB.Name = "B";
            threadA.Start();
            threadB.Start();
            Thread.CurrentThread.Name = "C";
            ThreadMethod();
            Console.ReadKey();
        }
        static object obj = new object();
        public static void ThreadMethod()
        {
            Monitor.Enter(obj);      //Monitor.Enter(obj)  锁定对象
            try
            {
                for (int i = 0; i < 500; i++)
                {
                    Console.Write(Thread.CurrentThread.Name); 
                }
            }
            catch(Exception ex){   }
            finally
            { 
                Monitor.Exit(obj);  //释放对象
            } 
        } 

语言 49

 

TryEnter(Object)TryEnter() 方法在尝试得到一个目标上的显式锁方面和
Enter()方法类似。然则,它不像Enter()方法这样会卡住执行。尽管线程成功进入第一区域那么TryEnter()方法会重返true. 和准备拿走指定对象的排他锁。看下边代码演示:

      我们可以通过Monitor.TryEnter(monster,
1000),该办法也可以避免死锁的发出,我们下边的事例用到的是该方法的重载,Monitor.TryEnter(Object,Int32),。 

语言 50

static void Main(string[] args)
        {                
            Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadA.Name = "A";
            Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 
            threadB.Name = "B";
            threadA.Start();
            threadB.Start();
            Thread.CurrentThread.Name = "C";
            ThreadMethod();
            Console.ReadKey();
        }
        static object obj = new object();
        public static void ThreadMethod()
        {
            bool flag = Monitor.TryEnter(obj, 1000);   //设置1S的超时时间,如果在1S之内没有获得同步锁,则返回false
        //上面的代码设置了锁定超时时间为1秒,也就是说,在1秒中后,
       //lockObj还未被解锁,TryEntry方法就会返回false,如果在1秒之内,lockObj被解锁,TryEntry返回true。我们可以使用这种方法来避免死锁
            try
            {
                if (flag)
                {
                    for (int i = 0; i < 500; i++)
                    {
                        Console.Write(Thread.CurrentThread.Name); 
                    }
                }
            }
            catch(Exception ex)
            {

            }
            finally
            {
                if (flag)
                    Monitor.Exit(obj);
            } 
        } 

语言 51

 Monitor.Wait和Monitor()Pause()

Wait(object)方法:释放对象上的锁并阻止当前线程,直到它再一次拿到该锁,该线程进入等待队列。
 Pulse方法:只有锁的当下主人可以运用 Pulse 向等待对象发出信号,当前所有指定对象上的锁的线程调用此措施以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不自然是收取到脉冲的线程)将收获该锁。
另外

        Wait 和 Pulse 方法必须写在 Monitor.Enter
和Moniter.Exit 之间。

上边是MSDN的表达。不知情看代码:

 首先我们定义一个攻击类,

语言 52

/// <summary>
    /// 怪物类
    /// </summary>
    internal class Monster
    {
        public int Blood { get; set; }
        public Monster(int blood)
        {
            this.Blood = blood;
            Console.WriteLine("我是怪物,我有{0}滴血",blood);
        }
    }

语言 53

然后在概念一个攻击类

语言 54

/// <summary>
    /// 攻击类
    /// </summary>
    internal class Play
    {
        /// <summary>
        /// 攻击者名字
        /// </summary>
        public string Name { get; set; } 
        /// <summary>
        /// 攻击力
        /// </summary>
        public int Power{ get; set; }
        /// <summary>
        /// 法术攻击
        /// </summary>
        public void magicExecute(object monster)
        {
            Monster m = monster as Monster;
            Monitor.Enter(monster);
            while (m.Blood>0)
            {
                Monitor.Wait(monster);
                Console.WriteLine("当前英雄:{0},正在使用法术攻击打击怪物", this.Name);
                if(m.Blood>= Power)
                {
                    m.Blood -= Power;
                }
                else
                {
                    m.Blood = 0;
                }
                Thread.Sleep(300);
                Console.WriteLine("怪物的血量还剩下{0}", m.Blood);
                Monitor.PulseAll(monster);
            }
            Monitor.Exit(monster);
        }
        /// <summary>
        /// 物理攻击
        /// </summary>
        /// <param name="monster"></param>
        public void physicsExecute(object monster)
        {
            Monster m = monster as Monster;
            Monitor.Enter(monster);
            while (m.Blood > 0)
            {
                Monitor.PulseAll(monster);
                if (Monitor.Wait(monster, 1000))     //非常关键的一句代码
                {
                    Console.WriteLine("当前英雄:{0},正在使用物理攻击打击怪物", this.Name);
                    if (m.Blood >= Power)
                    {
                        m.Blood -= Power;
                    }
                    else
                    {
                        m.Blood = 0;
                    }
                    Thread.Sleep(300);
                    Console.WriteLine("怪物的血量还剩下{0}", m.Blood);
                }
            }
            Monitor.Exit(monster);
        }
    }

语言 55

施行代码:

语言 56

    static void Main(string[] args)
        {
            //怪物类
            Monster monster = new Monster(1000);
            //物理攻击类
            Play play1 = new Play() { Name = "无敌剑圣", Power = 100 };
            //魔法攻击类
            Play play2 = new Play() { Name = "流浪法师", Power = 120 };
            Thread thread_first = new Thread(play1.physicsExecute);    //物理攻击线程
            Thread thread_second = new Thread(play2.magicExecute);     //魔法攻击线程
            thread_first.Start(monster);
            thread_second.Start(monster);
            Console.ReadKey();
        }

语言 57

输出结果:

语言 58

总结:

  先是种意况:

  1. thread_first首先拿到同步对象的锁,当执行到 Monitor.Wait(monster);时,thread_first线程释放自己对共同对象的锁,流放自己到等候队列,直到自己重新赢得锁,否则一直不通。
  2. 而thread_second线程一开始就竞争同步锁所以处于就绪队列中,这时候thread_second直接从稳妥队列出来得到了monster对象锁,开端举办到Monitor.PulseAll(monster)时,发送了个Pulse信号。
  3. 这时候thread_first接收到信号进入到妥善状态。然后thread_second继续往下实施到
    Monitor.Wait(monster,
    1000)时,这是一句非凡紧要的代码,thread_second将协调放逐到等候队列并释放自身对同步锁的独占,该等待安装了1S的超时值,当B线程在1S之内没有重新赢得到锁自动添加到就绪队列。
  4. 这时thread_first从Monitor.Wait(monster)的梗塞停止,再次回到true。起初施行、打印。执行下一行的Monitor.Pulse(monster),那时候thread_second如若1S的时间还没过,thread_second接收到信号,于是将自己添加到就绪队列。
  5. thread_first的一块儿代码块截至将来,thread_second再度拿到执行权, Monitor.Wait(m_smplQueue,
    1000)重临true,于是继续从该代码处往下执行、打印。当再一次实施到Monitor.Wait(monster,
    1000),又先河了步骤3。
  6. 次第循环。。。。

 
 其次种情形:thread_second首先拿到同步锁对象,首先实施到Monitor.PulseAll(monster),因为程序中没有索要等待信号进入就绪状态的线程,所以这一句代码没有意义,当执行到 Monitor.Wait(monster,
1000),自动将自己放逐到等候队列并在这边阻塞,1S
时间之后thread_second自动添加到就绪队列,线程thread_first得到monster对象锁,执行到Monitor.Wait(monster);时暴发短路释放同步对象锁,线程thread_second执行,执行Monitor.PulseAll(monster)时通知thread_first。于是又初步首先种情形…

Monitor.Wait是让眼前历程睡眠在临界资源上并释放独占锁,它只是等待,并不脱离,当等待截至,就要继续执行剩下的代码。

 

  3.0 使用Mutex类实现线程同步

   
  Mutex的隆起特色是足以跨应用程序域边界对资源举办垄断访问,即能够用来共同不同进程中的线程,这种效果自然这是以牺牲更多的系统资源为代价的。

  重要常用的三个形式:

 public virtual bool WaitOne()   阻止当前线程,直到近年来System.Threading.WaitHandle 收到信号获取互斥锁。

 public void ReleaseMutex()     释放 System.Threading.Mutex 一次。

  使用实例:

语言 59

    static void Main(string[] args)
        {
            Thread[] thread = new Thread[3];
            for (int i = 0; i < 3; i++)
            {
                thread[i] = new Thread(ThreadMethod1);
                thread[i].Name = i.ToString();
            }
            for (int i = 0; i < 3; i++)
            {
                thread[i].Start();
            }
            Console.ReadKey(); 
        } 

        public static void ThreadMethod1(object val)
        {
            mutet.WaitOne();    //获取锁
            for (int i = 0; i < 500; i++)
            {
                Console.Write(Thread.CurrentThread.Name); 
            } 
            mutet.ReleaseMutex();  //释放锁
        }

语言 60

 2、线程池

   
  上边介绍了介绍了通常采取的大部的多线程的例子,但在骨子里支付中动用的线程往往是大气的和更为复杂的,这时,每便都成立线程、启动线程。从性能上来讲,这样做并不完美(因为每使用一个线程就要成立一个,需要占用系统开发);从操作上来讲,每一回都要开动,相比较劳累。为此引入的线程池的概念。

  好处:

  1.调减在开立和销毁线程上所花的日子以及系统资源的开销 
 
2.如不使用线程池,有可能造成系统创立大气线程而导致消耗完系统内存以及”过度切换”。

在什么样情形下使用线程池? 

    1.单个任务处理的时刻相比短 
    2.索要处理的天职的数额大 

线程池最多管理线程数量=“处理器数 *
250”。也就是说,假使你的机器为2个2核CPU,那么CLR线程池的容量默认上限便是1000

透过线程池创设的线程默认为后台线程,优先级默认为Normal。

代码示例:

语言 61

    static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadMethod1), new object());    //参数可选
            Console.ReadKey();
        }

        public static void ThreadMethod1(object val)
        { 
            for (int i = 0; i <= 500000000; i++)
            {
                if (i % 1000000 == 0)
                {
                    Console.Write(Thread.CurrentThread.Name);
                } 
            } 
        }

语言 62

 

 

有关线程池的讲演请参考:

http://www.cnblogs.com/JeffreyZhao/archive/2009/07/22/thread-pool-1-the-goal-and-the-clr-thread-pool.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图