C#多线程(二)

摘要:
线程池通过共享和回收线程来分配这些内存,这样多个线程可以在非常细粒度的级别上运行,而不会影响性能。同时,线程池保持同时运行的工作线程总数的上限。如果活动线程太多,将增加操作系统的负担,并导致CPU缓存失效等问题。当达到上限时,它将被排队。在非活动空闲期间,管理器可以删除一些可疑线程以获得更好的吞吐量。
一、线程池

每次创建一个线程,都会花费几百微秒级别的时间来创建一个私有的局部栈,每个线程默认使用1M的内存。这个可以在使用Thread类的构造函数时设置:

  1. new Thread(new ThreadStart(Go), 2);  
  2. new Thread(new ParameterizedThreadStart(Go("hello")), 3);  

提供的两种构造函数方式都提供了对应的设置线程局部栈的大小。线程池通过共享和回收线程的方式来分配这些内存,这样可以使多线程运行在一个非常细粒度级别上而不影响性能。这对于充分利用多核处理器,使用分而治之的方式进行密集型计算的程序中很有用。同时线程池维护一个所有同时运行的工作线程总数的上限,如果有过多的活动线程就会加重操作系统的负担,使诸如CPU缓存失效等问题,当达到这个上限后,就要进行排队。这个线程队列使得任意并发的应用成为可能,如Web服务器就是这种原理。

有多种方式进入线程池:

  • 通过Task Parallel Library(.NET 4  TPL)
  • 通过调用ThreadPool.QueueUserWorkItem
  • 通过异步委托方式
  • 使用BackgroundWorker
下面的应用间接地使用了线程池:
  • WCF、Remoting、ASP.NET、ASMX webservice
  • System.Timers.Timer和System.Threading.Timer
  • .NET中以Async结束命名的方法
  • PLINQ
注意事项:
  • 不能对放入线程池中的线程命名,这会使得调试更加困难
  • 线程池中的线程总是后台线程
  • 阻塞线程池中的线程困难引发额外的延迟
可以使用下面的方式查询当前线程是否在线程池中:
  1. CurrentTread.IsThreadPoolThread  
二、进入线程池
1、使用TPL进入
使用TPL中的Task类就可以很简单的进入,使用Task.Factory.StartNew方法,传递一个目标函数的委托即可:
  1. static void Main(string[] args)  
  2. {  
  3.     Task.Factory.StartNew(Go);  
  4.   
  5.     Console.WriteLine("Is Thread Pool: " + Thread.CurrentThread.IsThreadPoolThread.ToString());  
  6.     Console.ReadKey();  
  7. }  
  8. static void Go()  
  9. {  
  10.     Console.Write("Is Thread Pool: " + Thread.CurrentThread.IsThreadPoolThread.ToString());  
  11. }  
C#多线程(二)第7张
Task.Factory.StartNew返回一个Task对象可以用来监控这个任务。
同时可以使用泛型类Task<TResult>,如下:
  1. static void Main(string[] args)  
  2. {  
  3.     Task<string> task = Task.Factory.StartNew<string>(Go);  
  4.       
  5.     Console.WriteLine("Is Thread Pool: " + Thread.CurrentThread.IsThreadPoolThread.ToString());  
  6.   
  7.     if (task.IsCompleted)  
  8.     {  
  9.         string result = task.Result;  
  10.         Console.WriteLine(task.IsCompleted.ToString() +  result);  
  11.     }  
  12.     Console.ReadKey();  
  13. }  
  14. static string Go()  
  15. {  
  16.     return Thread.CurrentThread.IsThreadPoolThread.ToString();  
  17. }  
C#多线程(二)第10张
如果在工作线程中出现异常,当获取Task的Result属性时会重新引发AggregateException异常,如果没有查询Result或者没有调用Wait方法,所有未处理的异常都会终止进程执行。
2、不使用TPL
对于.NET 4.0以前的版本是无法使用TPL的,必须使用ThreadPool.QueueUserWorkItem(类似Task类功能)和异步委托(类似Task<TResult>),但是这两个没有前面所述方法快、也没有那么方便和很好的扩展性。两种方式的使用如下:
  1. static void Main()  
  2. {  
  3.   ThreadPool.QueueUserWorkItem (Go);  
  4.   ThreadPool.QueueUserWorkItem (Go, 123);  
  5.   Console.ReadLine();  
  6. }  
  7.    
  8. static void Go (object data)   // data will be null with the first call.  
  9. {  
  10.   Console.WriteLine ("Hello from the thread pool! " + data);  
  11. }  
  1. static void Main()  
  2. {  
  3.   Func<stringint> method = Work;  
  4.   method.BeginInvoke ("test", Done, method);  
  5.   // ...  
  6.   //  
  7. }  
  8.    
  9. static int Work (string s) { return s.Length; }  
  10.    
  11. static void Done (IAsyncResult cookie)  
  12. {  
  13.   var target = (Func<stringint>) cookie.AsyncState;  
  14.   int result = target.EndInvoke (cookie);  
  15.   Console.WriteLine ("String length is: " + result);  
  16. }  

三、线程池的优化
线程池拥有的最大线程数可以通过ThreadPool.SetMaxThreads设置,默认值如下:
  • 32位的.NET4.0环境为1023个
  • 64位的.NET4.0环境为32768个
  • .NET 3.5为每个CPU核 250个
  • .NET2.0 为每个CPU核25个
线程池管理器在分配任务时通过添加新的线程来应对额外的工作量,当达到极限后开始排队。在非活动的闲置期,管理器可以删除一些可疑的线程从而可以得到更好的吞吐量。当然也可以使用Thread.SetMinThreads来设置最小的线程数目:最小线程数是优化线程池的高级方式,这个将指导管理器对于线程的分配没有延迟。提高最小数目可以当在有阻塞线程时提高并发量。
提高线程的最小数目并不能保证至少有相应个线程在线程池中,线程只会在有需要的时候创建。同时这个会指导池管理器创建到最小数目的线程。线程池在分配线程时会有半秒的延迟,之所以要有这个延迟就是为了防止突然的大量线程分配导致应用程序内存占用过大。线程池对于队列保持等待超过半秒时就会通过创建新的线程来响应请求,没半秒创建一个只到达到最大线程数。
这个半秒的延迟是有利也有弊的,弊端在于当一个阻塞的线程出现时并不需要延迟半秒,例如一个查询数据库或者下载网页的线程。这时需要告诉池管理器不要延迟进程线程分配,因此就需要用到SetMinThreads来设置:
  1. ThreadPool.SetMinThreads(50,50);  
 
参考:http://www.albahari.com/threading/

免责声明:文章转载自《C#多线程(二)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用CRectTracker类进行对象动态定位S/4 HANA中的ACDOCT和FAGLFLEXT下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Java 并发系列之七:java 阻塞队列(7个)

1.基本概念 2.实现原理 3.ArrayBlockingQueue 4.LinkedBlockingQueue 5.LinkedBlockingDeque 6.PriorityBlockingQueue 7.DelayQueue 8.SynchronousQueue 9.LinkedTransferQueue 10.小总结 11.t...

用Python实现多核心并行计算

平常写的程序,无论是单线程还是多线程,大多只有一个进程,而且只能在一个核心里工作。所以很多应用程序即使正在满载运行,在任务管理器中CPU使用量还是只有50%(双核CPU)或25%(四核CPU) 如果能让一个程序自己建立出多个进程,并且让它们并行运行,那么就可以在不同cpu核心上同时运行,进而实现并行计算啦。 Python的并行计算就是这么做的。 之前的理解...

C# 创建线程的多种方式之Thread类基础知识

1. Thread类创建线程 该类的构造函数可以接受ThreadStart委托参数(定义了无参,返回void的函数),以及ParameterizedThreadStart委托参数(定义了Object参数,返回void的函数)。 static void Main(string[] args) {...

Oracle Parallel 多线程

对于一个大的任务,一般的做法是利用一个进程,串行的执行,如果系统资源足够,可以采用parallel技术,把一个大的任务分成若干个小的任务,同时启用n个进程/线程,并行的处理这些小的任务,这些并发的进程称为并行执行服务器(parallel executeionserver),这些并发进程由一个称为并发协调进程的进程来管理。 启用Parallel前的忠告:只有...

Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

     声明:本文为原创博文,转载请注明出处。      Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们。在网络编程中,一般都是基于Reactor线程模型的变种,无论其怎么演化,其核心组件都包含了Reactor实例(提供事件注册、注销、通知功能)、多路复用器(由操作系统提供,比如kque...

【C/C++多线程编程之五】pthread线程深入理解

多线程编程之pthread线程深入理解       Pthread是 POSIX threads 的简称,是POSIX的线程标准。           前几篇博客已经能给你初步的多线程概念。在进一步学习线程同步等多线程核心知识之前,须要对多线程深入的理解。非常多人忽略或者回避这部分内容,直接的问题是学习者无法把握多线程编程的内在原理,理解的层次太浅。...