.NET 中的多线程的概念与线程池

为什么使用多线程

1.使用户界面能够随时相应用户输入

当某个应用程序在进行大量运算时候,为了保证应用程序能够随时相应客户的输入,这个时候我们往往需要让大量运算和相应用户输入这两个行为在不同的线程中进行。

2.效率原因

应用程序经常需要等待一些资源,如等待网络资源,等待io资源,等待用户输入等等。这种情况下使用多线程可以避免CPU长时间处于闲置状态。

用户态,内核态

线程内的资源有两种运行态,即用户态和内核态。某些运算可以在堆栈上进行,这种情况线程是在用户态运行的,某些需要高权限运行的指令,或者某些优先级很高的指令需要在操作系统内核中进行,这个时候线程会运行在内核态。出于安全原因,用户态和内核态的资源是不能够互相访问的,因此在用户态和内核态的切换过程中,我们需要进行相关上下文以及变量的复制,这意味的用户态和内核态的切换是以一定的时间消耗为代价的。

由于CPU是以时间片为单位进行线程的切换的,由于CPU的运算速度远大于内存的读写速度,因此CPU和内存之间通常有两级缓存,不同的线程的上下文访问的数据往往是不同的,这样线程的切换需要经常频繁的切换CPU缓存的内容,也需要更新线程的调度信息,这些都是需要花费一定的时间的,因此合理的使用多线程,来避免CPU不停的进行上下文切换。

System.Thread介绍

创建一个线程

创建每一个线程的时候,CLR都需要进行一系列的操作,如初始化线程的本地资源,为线程分配用户模式和内核模式下相应的堆栈,加载相应的托管,非托管资源等。

最简单常用的创建线程的方式是使用ThreadStart来创建线程,相关代码如下:


ThreadStart只需要一个委托即可,如果你善于使用匿名方法,也可以用匿名方法来代替委托,使用匿名方法的另一个好处是可以通过匿名方法的闭包特性来为新的线程传递参数。

虽然使用匿名方法的闭包特性可以很方便的为线程传递参数,但是也往往会带来一些不容易发现的问题,如下面的程序,由于i变量的共享,在运行的时候输出会有问题:


正确的写法应该是这样的:


线程异常的捕获

如果线程中可能需要捕获异常,那么我们不能这样做:


而是这样做:


System.Thread线程的成员

System.Threading.Thread帮助我们实现了一些线程的基本操作,如:

属性名称

说明

CurrentContext

获取线程正在其中执行的当前上下文。

CurrentThread

获取当前正在运行的线程。

ExecutionContext

获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。

IsAlive

获取一个值,该值指示当前线程的执行状态。

IsBackground

获取或设置一个值,该值指示某个线程是否为后台线程。

IsThreadPoolThread

获取一个值,该值指示线程是否属于托管线程池。

ManagedThreadId

获取当前托管线程的唯一标识符。

Name

获取或设置线程的名称。

Priority

获取或设置一个值,该值指示线程的调度优先级。

ThreadState

获取一个值,该值包含当前线程的状态。

 

 

方法名称

说明

Abort()    

终止本线程。

GetDomain()

返回当前线程正在其中运行的当前域。

GetDomainId()

返回当前线程正在其中运行的当前域Id。

Interrupt()

中断处于 WaitSleepJoin 线程状态的线程。

Join()

已重载。阻塞调用线程,直到某个线程终止时为止。

Resume()

继续运行已挂起的线程。

Start()  

执行本线程。

Suspend()

挂起当前线程,如果当前线程已属于挂起状态则此不起作用

Sleep()  

把正在运行的线程挂起一段时间。

前台线程vs后台线程

这里我们单独提一下前台线程和后台线程。在CLR中,线程分为前台线程和后台线程,当所有前台的线程执行完之后,CLR会强制结束所有正在运行的后台线程,并且不会出现任何异常。

因此你应该使用前台线程来做一些必须完成的任务,比如把流从内存中写到磁盘上。后台线程可以做一些不那么重要的事情。一旦线程对象的生命周期开始,你就不能修改IsBackground值。

 由于线程是非常昂贵的资源,我们经常需要控制允许多少线程同时运行,如何控制线程的生命周期,如何管理线程,这里我们引入了线程池的概念。

.NET 中的多线程 线程池

由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源。最好的办法是使用线程池,线程池能够避免当前进行中大量的线程导致操作系统不停的进行线程切换,当线程数量到达了我们设置的上限,线程会自动排队等待,当线程资源可用时,队列中的线程任务会依次执行,如果没有排队等候的资源,线程会变为闲置状态。

使用ThreadPool来访问线程池


这种做法可以让我们不用那么复杂的去实现创建,重用线程的逻辑,但是也有一些限制,比如由他内置的方法,我们不知道什么时候线程池里面的任务会结束,也不能获取线程的返回值。为了解决这些问题,微软引入了一个新的概念。

使用Task来访问线程池

引入了Task之后,你可以用如下实现来替代ThreadPool


这些实现都是等价的。Task本身实现了很多ThreadPool不能做的事情。

使用Task来获得线程的返回值


使用Task来等待线程结束


更多Task同步编程的使用,请参见(还没写,先给自己挖个坑O(∩_∩)O)。

异步委托

ThreadPool.QueueUserWorkItem没有提供一种简单的机制来获取线程的返回值。异步委托解决了这个问题,支持了传入一系列的参数。此外,异步委托中没有处理的异常会很方便的在调用线程的重新抛出(在调用EndInvoke的时候),因此不需要显示的处理。

通过异步委托来执行任务主要分一下几步:

初始化并声明一个你想要执行的委托

在委托上调用BeginInvoke,把返回值保存为IAsyncResult中

调用BeginInvoke不会阻塞当前线程,因此你可以在调用完之后执行其他你想要同步的操作

当你需要获取委托的返回值时,调用EndInvoke方法,把IAsyncResult传入EndInvoke中

阻塞的方式执行异步委托


EndInvoke主要做3件事: 1. 等待异步委托完成 2. 接收返回值 3. 把异步线程中未处理的异常在当前线程中重新抛出。

非阻塞的方式执行异步委托

你也可以在调用BeginInvoke的时候指定一个回调方法,这个方法会在异步委托结束的时候自动调用。这样异步委托就像是一个后台线程一样自动执行,不需要主线程等待。只需要在BeginInvoke的时候做一些额外的操作即可实现这种操作。


关于线程池

Jeffery在C# via CLR
Chapter27中针对线程池的使用给出了一些建议。目前我们允许开发者来指定一个线程池的最大线程数。但是事实证明,我们往往不应该为一个线程池指定线程的上限,否则可能会出现程序死锁或者饿死的状态。比如你可能设置了1000个线程,但是某一时刻正好有第1001个线程需要等待所有线程结束才能执行,这种情况如果你限制了线程池线程的个数,就会出现死锁。从开发的另一个角度说,你也不应该限制一个进程使用多少资源,比如一个进程可以使用多少内存,使用多少带宽.因此虽然目前你可以通过GetMaxThreads,
SetMaxThreads,GetMinThreads,SetMinThreads
,GetAvailableThreads来进行线程个数的限制,但是他仍然不建议大家这样做。这些限制可能会让你的程序运行的更慢。

时间: 2024-09-30 10:14:05

.NET 中的多线程的概念与线程池的相关文章

《Java多线程编程核心技术》——1.1节进程和多线程的概念及线程的优点

1.1 进程和多线程的概念及线程的优点 本节主要介绍在Java语言中使用多线程技术.但讲到多线程这个技术时不得不提及"进程"这个概念,"百度百科"里对"进程"的解释如图1-1所示. 初看这段文字会觉得十分的抽象,难以理解,但如果你看到图1-2所示的内容,那么你对进程还不能理解吗? 难道可以将一个正在操作系统中运行的exe程序理解成一个"进程"吗?没错! 通过查看"Windows任务管理器"中的列表,完全可以

5天不再惧怕多线程——第五天 线程池

说到多线程,不可不说线程池,C#中关于池的概念很多,今天来整理下ThreadPool的使用. 是的,如果你很懒,如果你的执行任务比较短,如果你不想对线程做更精细的控制,那么把这些繁琐的东西丢给线程池吧. 一:ThreadPool 好了,下面看看TheadPool下有哪些常用的方法. 1:GetMaxThreads,GetMinThreads 首先我们肯定好奇线程池到底给我们如何控制线程数,下面就具体的看一看. class Program { static void Main(string[] a

艾伟:C#多线程学习(四) 多线程的自动管理(线程池)

本系列文章导航 C#多线程学习(一) 多线程的相关概念 C#多线程学习(二) 如何操纵一个线程 C#多线程学习(三) 生产者和消费者 C#多线程学习(四) 多线程的自动管理(线程池) C#多线程学习(五) 多线程的自动管理(定时器) C#多线程学习(六) 互斥对象 在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPool(线程池)来解决: 另一种情况:线程平时都处于休眠状态,只是周期性地被

C#多线程学习(四)多线程的自动管理(线程池)

在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPool(线程池)来解决: 另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒 这一般使用Timer(定时器)来解决: ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要 Windows 2000 以上系统支持,因为其中某些方法调用了只有高版本的Windows才有的API函数. 将线程安放在线程池里,需

多线程-使用多个线程池还是一个线程池

问题描述 使用多个线程池还是一个线程池 最近在技术理论上遇到一个问题,不知道怎么解决. 问题描述:我们交易平台有4个商品(A.B.C.D)需要进行现在交易,交易的核心方法dealorder是 一个加了锁和事务的方法,而且该类是针对每一个商品的,即最多同时可以执行4个dealOrder方法,即每个商品执行一个该方法. @Transactional public synchronized Message dealOrder() CPU核心数是固定的,假如为6核心,目前就存在一个问题, 情况1:假如说

C#中使用多线程编程之线程池

编程|多线程 1.     引言 近来在研究C#多线程编程碰到了线程池的概念.不懂,我搜,于是在MSDN和CSDN上寻寻觅觅一番终于搞明白,"缘"来如此,安装本人理解修改后写下这篇文章,希望对后来者有所帮助.   2.     线程池的概念 可以使用线程池来根据应用程序的需要更为有效地利用多个线程.许多应用程序使用多个线程,但这些线程经常在休眠状态中耗费大量的时间来等待事件发生,编程者手动管理多个线程也是一件比较麻烦的事情.事实上,使用线程池就是为应用程序提供一个由系统管理的辅助线程池

《C#多线程编程实战(原书第2版)》——第3章 使用线程池 3.1 简介

第3章 使用线程池 在本章中,我们将描述多线程中使用共享资源的常用技术.你将学到以下内容: 在线程池中调用委托 向线程池中放入异步操作 线程池与并行度 实现一个取消选项 在线程池中使用等待事件处理器及超时 使用计时器 使用BackgroundWorker组件 3.1 简介 在之前的章节中我们讨论了创建线程和线程协作的几种方式.现在考虑另一种情况,即只花费极少的时间来完成创建很多异步操作.正如在第1章的简介小节中讨论过的一样,创建线程是昂贵的操作,所以为每个短暂的异步操作创建线程会产生显著的开销.

多线程之线程池概述(一)

java在JDK1.5之后引入了并发计算框架,java.util.concurrent.这个框架大大减轻了简化了多线程的开发工作.一个线程大概有五种状态:新建状态(New).可运行状态(Runnable,也叫做运行状态).阻塞状态(Blocked).等待状态(Waiting).结束状态(Terminated).线程的状态只能由新建转变为了运行状态后才能被阻塞或者等待状态.线程的状态流转如图所示: 注意:这里把等待状态给细分了一下.把等待状态分为了等待池和等锁池. 线程的运行时间可以分为三个部分:

Java多线程和线程池

版权声明:本文为博主原创文章,转载注明出处http://blog.csdn.net/u013142781 1.为什么要使用线程池 在Java中,如果每个请求到达就创建一个新线程,开销是相当大的.在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多.除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源.如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或"切换过度"而导致系统资源不足.为了防止资源