C#多线程之旅(3)——线程池

简介: C#多线程之旅(3)——线程池


阅读目录

v博客前言

先交代下背景,写《C#多线程之旅》这个系列文章主要是因为以下几个原因:1.多线程在C/S和B/S架构中用得是非常多的;2.而且多线程的使用是非常复杂的,如果没有用好,容易造成很多问题。

图片.png

v写在前面

多线程,有利也有弊,使用需谨慎。

v正文开始

 

原文地址:C#多线程之旅(3)——线程池

C#多线程之旅(1)——介绍和基本概念

C#多线程之旅(2)——创建和开始线程

C#多线程之旅(3)——线程池

C#多线程之旅(4)——APM初探

C#多线程之旅(5)——同步机制介绍

C#多线程之旅(6)——详解多线程中的锁

更多文章正在更新中,敬请期待......

C#多线程之旅(3)——线程池

代码下载

Thread_博客园_cnblogs_jackson0714.zip

第一篇~第三篇的代码示例:

图片.png

源码地址:https://github.com/Jackson0714/Threads

一、介绍

无论你什么时候开始一个线程,几百毫秒会花在整理一个新的local variable stack。每一个线程默认会消耗1MB的内存。线程池通过分享和回收线程来削减这些开销,允许多线程被应用在一个非常颗粒级的级别而没有性能损失。当充分利用多核系统去执行密集型计算的并行代码时这是非常有用的。

线程池也会在线程的总数量上保持一个限制,从而使线程能够更平稳地运行。太多的线程将会造成管理负担和使CPU缓存是小,从而造成操作系统不能运行。一旦一个限制到达,job排队等待直到另外一个完成才开始。这会使任意的并行应用程序成为可能,比如一个web server(同步方法是高级技巧,可以更高效地使用线程池中的线程)。

下面是几种方式进入线程池:

  1. 通过Task Parallel Library.NET 4.0
  2. 通过调用ThreadPool.QueueUserWorkItem
  3. 通过asynchronous delegates
  4. 通过BackgroundWorkder

下面的结构直接使用线程池:

  1. WCF,Remoting,ASP.NET,ASMX Web Services application servers
  2. System.Timers.Timer and System.Threading.Timer
  3. Framework methods Async结束,比如WebClient(the event-based asynchronous pattern)和大部分的BeginXXX方法(the asynchronous programming model pattern)
  4. PLINQ

Task Parallel Library(TPL)PLINQ是充分有效的和高等级的,甚至当线程池是不重要的时候,你也会想使用它们去协助处理多线程。

现在我们简单的看一下我们怎样使用Task类来实现一个简单的运行在线程池上的委托。

当使用线程池时需要注意下面的事情:

  1. 你不能设置一个线程的名字,因为设置线程的名字将会使调试更困难(当你在VS线程窗口中调试时,即使你可以附加一个描述)。
  2. 线程池中的线程总是后台线程(这通常不是问题)。
  3. 在应用程序的开始期间,阻塞一个线程可能会触发一个延迟,除非你调用ThreadPool.SetMinThreads

你不能任意地改变池中的线程的优先级-因为当它释放会池中的时候,优先级会被还原为正常状态。

你可以通过属性Thread.CurrentThread.IsThreadPoolThread的属性查询线程是否是正在运行的一个池中的线程

回到顶部

二、通过TPL进入线程池

你可以使用在TaskParallel Library中的Task类来轻松的进入线程池。这个Task类在Framework 4.0中有介绍:如果你对老的结构比较熟悉,考虑用非泛型的Task类替换ThreadPool.QueueUserWorkItem,将Asunchoronous delgates替换为泛型Task<TResult>。最新的结构速度更快,更方便,而且更复杂。

为了使用非泛型的任务类,调用Task.Factory.StartNew方法,将方法传进委托中。

Task.Factory.StartNew会返回一个Task对象,你可以使用它去监控这个task,比如,你可以调用它的wait方法等待它直到它完成。

static void Main(string[] args)
{
    Task task =  Task.Factory.StartNew(Go);
    task.Wait();
    Console.ReadKey();
}
static void Go()
{
    Console.WriteLine("From the thread pool start...");
    Thread.Sleep(3000);
    Console.WriteLine("From the thread pool end");
}

当你调用taskWait方法时,一个未处理的异常会很容易地重新抛出到宿主线程上。(如果你不调用Wait方法而是放弃这个task,一个未处理的异常将会关闭掉这个进程)

泛型Task<Tresult>类是非泛型Task的子类。它让你从这个已经完成执行的task中得到一个返回值。在下面的例子中,我们使用Task<TResult>来下载一个web page

static void Main(string[] args)
{
    Task<string> task = Task.Factory.StartNew<string>(
        () => DownloadString("http://www.baidu.com"));
    //调用其他方法
    //
    //可以用task的Result的属性来获得task返回值。
    //如果这个任务还在运行,当前的主线程将会被阻塞,直到这个任务完成。
    string result = task.Result;
}
static string DownloadString(string uri)
{
    using(var wc = new System.Net.WebClient())
    {
        return wc.DownloadString(uri);
    }
}

Task Parallel Library有许多的功能,特别是提升多核处理器的性能。我们会在并行编程中继续讨论TPL

回到顶部

三、不用TPL进入到线程池

如果你的应用程序是.NET Framework的早期版本(4.0之前的版本),你将不能使用TPL。你必须使用老的结构进入线程池:

ThreadPool.QueueUserWorkItemasynchoronous delegates.两者的不同点是asynchronous delegates让你从线程那里返回数据。Asynchronous delegates收集任何exception返回给调用者。

要使用QueueUserWorkItem,只需调用这个方法的运行在线程池上的委托。

static void Main(string[] args)
{
    ThreadPool.QueueUserWorkItem(Go);
    ThreadPool.QueueUserWorkItem(Go, 123);
    Console.ReadKey();
}
static void Go(object data)
{
    Console.WriteLine("A from thread pool! " + data);
}

图片.png

我们的目标方法Go,必须接收一个简单object类型的参数(为了满足waitCallBack委托)。这将提供一个简单的方式传递数据到方法中,就像是ParameterizedThreadStart。不像TaskQueueUserWorkItem不会返回一个对象去帮助你之后管理执行。还有,你必须显式在目标方法的代码中写处理异常的代码-因为未处理的异常将会终止程序。

ThreadPool.QueueUserWorkItem没有提供从一个已经完成的线程中得到它的返回值的机制。Asynchronous delegate invocations(asynchronous delegates for short)解决了这个问题,允许任何个数类型化的参数在两个方向传递。此外,在asynchronous delegates上未处理的异常很方便地在原始线程上重新抛出(更准确地说,这个线程叫做EndInvoke,因此不需要显示处理。

不要混淆asynchronous delegates和asynchronous method(方法以Begin和End开头的,比如File.BeginRead/File.EndRead)。Asynchronous methods表面上按照简单的协议,但是它们的存在是为了解决一个更困难的问题。

下面是怎样通过一个asynchronous delegate开始一个worker task:

  1. 实例化一个委托,该委托针对你想要并行运行的method(典型的是预定义Func delegates其中的一种)。
  2. delegate上调用BeginInvoke,保存它的IAsyncResult返回值。BeginInvoke立即返回给调用者。当其他池中的线程正在运行的时候,你可以执行其他动作。
  3. 当你需要这个结果,在delegate上调用EndInvoke,传递已保存的IAsyncResult对象。

在下面的例子中,我们使用一个asynchronous delegate invocation运行一个与主线程同时运行的简单方法,这个方法返回一个字符串的长度:

static void Main(string[] args)
{
    Func<string, int> t = Go;
    IAsyncResult result = t.BeginInvoke("test", null, null);
    //
    // ... 这里可以执行其他并行的任务
    //
    int length = t.EndInvoke(result);
    Console.WriteLine("String lenth is: " + length);
    Console.ReadKey();
}
static int Go(string messsage)
{
    return messsage.Length;
}

EndInvoke做三件事情。第一,如果asynchronous delegate没有完成执行,则一直等待它完成。第二,接收返回值(以及任何ref或者out参数)。第三,返回任何未处理的线程异常给调用它的线程。

注意:如果你用asynchronous delegate调用的方法没有返回值,你在技术上需要调用EndInvoke。在实践中,这是开放的辩论;没有Endinvoke报警去管理处罚未编译者!如果你选择不去调用EndInvoke,然而,你需要考虑在线程的异常去避免静默失败。

当你调用BeginInvoke方法时,可以指定一个call back delegate-一个可以接收一个IAsyncResult 对象的方法,它会在委托方法完成后被自动调用这个允许正在发动的线程忘记asynchronous delegate,但它在call back结束时需要一点额外的工作。

v写在最后

线程池的使用的提升还没有写,最近两年作息不规律,程序员得养好身体,早睡早起,睡觉睡觉。希望这篇博客能帮到大家,希望得到园友们的支持!




相关文章
|
1月前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
112 38
|
23天前
|
Java
.如何根据 CPU 核心数设计线程池线程数量
IO 密集型:核心数*2 计算密集型: 核心数+1 为什么加 1?即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费。
23 4
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
56 1
C++ 多线程之初识多线程
|
1月前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
76 2
|
1月前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
84 4
|
1月前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
230 2
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
26 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
22 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
35 2
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
41 1