CLR 线程池

CLR 线程池: CLR初始化时线程池是怎样的,在什么情况下创建,什么情况下收回,销毁。

  • 线程池在CLR中,CLR中的多个AppDomain共享这个线程池。

  • CLR在初始化时线程池中没有线程。

  • 线程池内部维护了一个操作请求队列。

  • 调用线程池某个方法,将一个记录项(entry)追加到线程池队列中。

  • 线程池的内部代码将记录项派发给一个线程池线程。

  • 如果线程池中没有就创建一个,线程执行完后,再收回到线程池中。

  • 如果应用程序向线程池发了很多请求,一个线程忙不过来就会再创建更多的线程。

  • 当一个线程池线程闲着没事一段时间后,线程会自己醒来终止自己以释放资源。

 

ThreadPool 的使用:

using System;
using System.Threading;
public static class Program {
public static void Main() {
       Console.WriteLine("Main thread: queuing an asynchronous operation");
       ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5);
       Console.WriteLine("Main thread: Doing other work here...");
       Thread.Sleep(10000); // Simulating other work (10 seconds)
       Console.WriteLine("Hit <Enter> to end this program...");
       Console.ReadLine();
  }
   // This method's signature must match the WaitCallback delegate
   private static void ComputeBoundOp(Object state) {
       // This method is executed by a thread pool thread
       Console.WriteLine("In ComputeBoundOp: state={0}", state);
       Thread.Sleep(1000); // Simulates other work (1 second)
       // When this method returns, the thread goes back
       // to the pool and waits for another task
  }
}

 

执行上下文

执行上下文 是每个线程都有的,它包含了:安全设置、宿主设置、 以及逻辑调用上下文数据。执行上下文设置 会影响线程执行它的代码。那是怎么影响的呢?

当一个线程(主)使用另一个线程(辅)执行任务时,前者的执行上下文应该流向(复制)辅助线程。

确保辅助线程的操作 和主线程是相同的安全设置和宿主设置。但是主线程将 上下文流向 辅助线程 这个操作很耗时间。

System.Threading 命名空间中的ExecutionContext 类,可以控制主线程 如何将执行上下文流向另一个辅助线程。

    public sealed class ExecutionContext : IDisposable, ISerializable {
  [SecurityCritical] public static AsyncFlowControl SuppressFlow();
   public static void RestoreFlow();
   public static Boolean IsFlowSuppressed();
   // Less commonly used methods are not shown
}

它可以阻止执行上下文流动以提升应用程序的性能。

public static void Main() {
   // Put some data into the Main thread's logical call context
   CallContext.LogicalSetData("Name", "Jeffrey");
   // Initiate some work to be done by a thread pool thread
   // The thread pool thread can access the logical call context data
   ThreadPool.QueueUserWorkItem(state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name")));
   // Now, suppress the flowing of the Main thread's execution context
   ExecutionContext.SuppressFlow();
   // Initiate some work to be done by a thread pool thread
   // The thread pool thread CANNOT access the logical call context data
   ThreadPool.QueueUserWorkItem(state => Console.WriteLine("Name={0}", CallContext.LogicalGetData("Name")));
   // Restore the flowing of the Main thread's execution context in case
   // it employs more thread pool threads in the future
   ExecutionContext.RestoreFlow();
  ...
   Console.ReadLine();
}

当编译完成后运行的结果如下:

Name=Jeffrey
Name=

 

协作式取消和超时

  • 取消操作首先要创建一个 System.Threading.CancellationTokenSource 对象。这个对象包含了和管理取消有关的所有状态。

  • CancellationTokenSource 对象的 Token 属性 获得 一个或多个 CancellationToken 实例,传给你的操作就可以取消。

对一个任务的取消操作的例子如下:

public struct CancellationToken { // A value type
   public static CancellationToken None { get; } // Very convenient
   public Boolean IsCancellationRequested { get; } // Called by non•Task invoked operations
   public void ThrowIfCancellationRequested(); // Called by Task•invoked operations
   // WaitHandle is signaled when the CancellationTokenSource is canceled
   public WaitHandle WaitHandle { get; }
   // GetHashCode, Equals, operator== and operator!= members are not shown
   public Boolean CanBeCanceled { get; } // Rarely used
   public CancellationTokenRegistration Register(Action<Object> callback, Object state, Boolean useSynchronizationContext); // Simpler overloads not shown
}

 

任务

ThreadPool 的 QueueUserWorkItem 有许多限制,没有内建的机制让你知道操作在什么时候完成,也没有机制在操作完成时获得返回值。task 任务可以替代ThreadPool。

ThreadPool.QueueUserWorkItem(ComputeBoundOp, 5); // Calling QueueUserWorkItem
new Task(ComputeBoundOp, 5).Start(); // Equivalent of preceding using Task
Task.Run(() => ComputeBoundOp(5)); // Another equivalent

为了创建一个Task,需要调用构造器并传递一个Action或Action<Object>委托。这个委托就是你想要执行的操作。

可以向构造器传递一些TaskCreationOptions 标志类控制Task 的执行方式。TaskCreationOptions 枚举类型定义了一组可按位OR 的标志,定义如下:

[Flags, Serializable]
public enum TaskCreationOptions {
   None = 0x0000,// The default
   // Hints to the TaskScheduler that you want this task to run sooner than later.
   PreferFairness = 0x0001,
   // Hints to the TaskScheduler that it should more aggressively create thread pool threads.
   LongRunning = 0x0002,
   // Always honored: Associates a Task with its parent Task (discussed shortly)
   AttachedToParent = 0x0004,
   // If a task attempts to attach to this parent task, it is a normal task, not a child task.
   DenyChildAttach = 0x0008,
   // Forces child tasks to use the default scheduler as opposed to the parent’s scheduler.
   HideScheduler = 0x0010
}

 

等待任务完成并获取结果

private static Int32 Sum(Int32 n) {
   Int32 sum = 0;
   for (; n > 0; n--)
   checked { sum += n; } // if n is large, this will throw System.OverflowException
   return sum;
}

现在构造一个Task<TResult> 对象,并为泛型TResult 参数传递计算限制操作的返回类型。开始任务之后,可等待它完成并获得结果。

// Create a Task (it does not start running now)
Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000000000);
// You can start the task sometime later
t.Start();
// Optionally, you can explicitly wait for the task to complete
t.Wait(); // FYI: Overloads exist accepting timeout/CancellationToken
// You can get the result (the Result property internally calls Wait)
Console.WriteLine("The Sum is: " + t.Result); // An Int32 value
  • 调用 Wait 方法 或者 Result属性时,这些成员会抛出一个System.AggergateException 对象。

  • AggregateException 类型封装了异常对象的一个集合,该类的InnerExceptions属性包含一个异常列表。

  • 如果一直不调用Wait 或 Result ,或者一直不查询Task 的 Exception 属性,代码就一直注意不到这个异常的发生。

  • 为了帮你检测到该异常,可以向 TaskerScheduler 的静态 UnobservedTaskException 事件登记一个回调方法。

  • 每次当一个Task 被垃圾回收时,如果存在一个没有被注意到的异常,CLR 的终结器线程就会引发这个事件。

  • 一旦引发就会向你注册的事件处理方法中传递一个UnobservedTaskExceptionEventArgs对象,这其中包含你没有注意到的AggregateException。

  • Task 的两个静态方法 WaitAny 和 WaitALl ,它们都会阻塞调用线程,直到数组中所有的Task 对象完成。都可以通过CancellationToken 取消,并抛出一个 OperationCanceledException。

 

任务完成时自动启动新任务

伸缩性好的软件不应该使线程阻塞。调用Wait ,或者在任务还没有完成时查询任务的Result 属性。这样操作可能会造成线程池创建新线程,这增大了资源的消耗,也不利于性能和伸缩性。幸好还有更好的办法知道线程什么时候结束,还能在结束时启动新的 Task。

// Create and start a Task, continue with another task
Task<Int32> t = Task.Run(() => Sum(CancellationToken.None, 10000));
// ContinueWith returns a Task but you usually don't care
Task cwt = t.ContinueWith(task => Console.WriteLine("The sum is: " + task.Result));

ContinueWith 方法,它返回的是对新 Task 对象的引用。可以用这个对象调用各种成员。还有 Task 对象内部包含了ContinueWith 任务的一个集合。所以可用一个Task 对象来多次调用COntinueWith。任务完成时,所有ContinueWith 任务都会进入线程池的队列中。

TaskContinueationOptions 枚举值 可在调用ContinueWith 时传进去。

[Flags, Serializable]
public enum TaskContinuationOptions {
   None = 0x0000,// The default
   // Hints to the TaskScheduler that you want this task to run sooner than later.
   PreferFairness = 0x0001,
   // Hints to the TaskScheduler that it should more aggressively create thread pool threads.
   LongRunning = 0x0002,
   // Always honored: Associates a Task with its parent Task (discussed shortly)
   AttachedToParent = 0x0004,
   // If a task attempts to attach to this parent task, an InvalidOperationException is thrown.
   DenyChildAttach = 0x0008,
   // Forces child tasks to use the default scheduler as opposed to the parent’s scheduler.
   HideScheduler = 0x0010,
   // Prevents completion of the continuation until the antecedent has completed.
   LazyCancellation = 0x0020,
   // This flag indicates that you want the thread that executed the first task to also
   // execute the ContinueWith task. If the first task has already completed, then the
   // thread calling ContinueWith will execute the ContinueWith task.
   ExecuteSynchronously = 0x80000,
   // These flags indicate under what circumstances to run the ContinueWith task
   NotOnRanToCompletion = 0x10000,
   NotOnFaulted = 0x20000,
   NotOnCanceled = 0x40000,
   // These flags are convenient combinations of the above three flags
   OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted,
   OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled,
   OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled,
}

调用 ContinueWith 时,可用 TaskContinuationOptions.OnlyOnCanceled 标志指定新任务只有在第一个任务被取消时才执行。类似地 OnlyOnFaulted 只有在第一个任务抛出未处理的异常时才执行。OnlyOnRanToCompletion 只有在第一个任务顺利执行完成时才执行。

默认情况下,如果不指定上述任何标志,则新任务无论如何都会运行,不管第一任务如何完成。

 

任务可以启动子任务

任务支持父/子关系,如下代码所示:

Task<Int32[]> parent = new Task<Int32[]>(() => {
   var results = new Int32[3]; // Create an array for the results
   // This tasks creates and starts 3 child tasks
   new Task(() => results[0] = Sum(10000), TaskCreationOptions.AttachedToParent).Start();
   new Task(() => results[1] = Sum(20000), TaskCreationOptions.AttachedToParent).Start();
   new Task(() => results[2] = Sum(30000), TaskCreationOptions.AttachedToParent).Start();
   // Returns a reference to the array (even though the elements may not be initialized yet)
   return results;
});
// When the parent and its children have run to completion, display the results
var cwt = parent.ContinueWith(
parentTask => Array.ForEach(parentTask.Result, Console.WriteLine));
// Start the parent Task so it can start its children
parent.Start();

一个任务创建的一个或多个 Task 对象默认是顶级任务,他们与创建它们的任务无关。但 TaskCreationOptions.AttachedToParent 标志将一个Task 和创建它的 Task关联,结果是除非所有子任务(以及子任务的子任务)结束运行,否则创建(父任务)不认为已经结束。

 

Task内部揭秘

每个Task 对象都有一组字段,这些字段构成了任务的状态。其中包括 :

  • 一个Int32 ID;(代表Task 唯一ID 的 Int32 字段。从 1 开始,每分配一个ID都递增1。系统分配的代表 Task 执行状态的一个 Int32 )

  • 对父任务的引用、

  • 对Task创建时指定的 TaskScheduler 的引用、

  • 对回调方法的引用、

  • 对要传给回调方法对象的引用、

  • 对 ExecutionContext 的引用以及对 ManualResetEventSlim 对象的引用。

另外,每个 Task 对象都有对根据需要创建的补充状态的引用。补充状态包含:

  • 一个CancellationToken、

  • 一个 ContinueWithTask 对象集合、

  • 为抛出未处理异常的子任务而准备的一个Task 对象集合等。

Task 很好用,但也是有代价的。必须为所有这些状态分配内存。如果不需要任务的附加功能,那么使用 ThreadPool.QueueUserWorkItem 能获得更好的资源利用率。

Task 和 Task<TResult> 类实现了 IDisposable 接口。如今 ,所有Dispose 方法所做的都是关闭 ManualResetEventSlim 对象。 但可以从 Task 和 Task<TResult> 派生的类,在这些类中分配它们自己的资源,并在它们重写的 Dispose 方法中释放这些资源。但不建议为Task对象显示调用 Dispose。

在一个 Task 对象的存在期间,可查询 Task 的只读 Status 属性了解 它在其生存期的什么位置。该属性返回一个 TaskStatus 值。

public enum TaskStatus {
   // These flags indicate the state of a Task during its lifetime:
   Created, // Task created explicitly; you can manually Start() this task
   WaitingForActivation,// Task created implicitly; it starts automatically
   WaitingToRun, // The task was scheduled but isn’t running yet
   Running, // The task is actually running
   // The task is waiting for children to complete before it considers itself complete
   WaitingForChildrenToComplete,
   // A task's final state is one of these:
   RanToCompletion,
   Canceled,
   Faulted
}

当任务完成时,状态变成 以下状态之一:RanToCompletion、Canceled 或 Faulted。如果任务完成,可通过Task<TResult> 的Result 属性来查询任务结果。Task 或 Task<TResult>出错时,可查询 Task 的 Exception 属性获取异常,该属性总是返回一个AggregateException 对象,对象的 InnerExceptions 集合包含了所有未处理的异常。

为了简化代码,Task 提供了几个只读 Boolean 属性,包括IsCanceled 、 IsFaulted和 Iscompleted。注意当 Task 处于 RanToCompletion ,Canceled 或 Faulted 状态时,IsCompleted 返回 true。判断一个 Task 是否成功完成 最简单的办法是使用如下代码:

if(task.Status == TaskStatus.RanToCompletion) ...

 

任务工厂 - TaskFactory

有时需要创建一组共享相同配置的 Task 对象。为避免机械地将相同的参数传给每个Task 的构造器,可以创建一个任务工厂来封装通用的配置。System.Threading.Tasks 命名空间定义了一个 TaskFactory 类型和一个 TaskFactory<TResult> 类型。

以下代码延时了如何使用一个TaskFactory:

Task parent = new Task(() => {
   var cts = new CancellationTokenSource();
   var tf = new TaskFactory<Int32>(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
   // This task creates and starts 3 child tasks
   var childTasks = new[] {
       tf.StartNew(() => Sum(cts.Token, 10000)),
       tf.StartNew(() => Sum(cts.Token, 20000)),
       tf.StartNew(() => Sum(cts.Token, Int32.MaxValue)) // Too big, throws OverflowException
  };
   // If any of the child tasks throw, cancel the rest of them
   for (Int32 task = 0; task < childTasks.Length; task++)
  childTasks[task].ContinueWith(t => cts.Cancel(), TaskContinuationOptions.OnlyOnFaulted);
   // When all children are done, get the maximum value returned from the
   // non-faulting/canceled tasks. Then pass the maximum value to another
   // task that displays the maximum result
   tf.ContinueWhenAll(
       childTasks,
       completedTasks =>
       completedTasks.Where(t => t.Status == TaskStatus.RanToCompletion).Max(t => t.Result),
      CancellationToken.None).ContinueWith(t=>Console.WriteLine("The maximum is: " + t.Result),
  TaskContinuationOptions.ExecuteSynchronously);
});

// When the children are done, show any unhandled exceptions too
parent.ContinueWith(p => {
   // I put all this text in a StringBuilder and call Console.WriteLine just once
   // because this task could execute concurrently with the task above & I don't
   // want the tasks' output interspersed
   StringBuilder sb = new StringBuilder(
   "The following exception(s) occurred:" + Environment.NewLine);
   foreach (var e in p.Exception.Flatten().InnerExceptions)
  sb.AppendLine(" "+ e.GetType().ToString());
   Console.WriteLine(sb.ToString());
}, TaskContinuationOptions.OnlyOnFaulted);
// Start the parent Task so it can start its children
parent.Start();

 

任务调度器

任务基础结构非常灵活,其中TaskScheduler 对象功不可没。TaskScheduler 对象负责执行被调度的任务。FCL 提供了 两个派生自TaskScheduler 的类型:

  • 线程池任务调度器 ( thread pool task scheduler)

  • 同步上下文任务调度器 (synchronization context task scheduler)

默认情况下是线程池任务调度器,这个任务调度器将任务调度给线程池的工作者线程。

同步上下文任务调度器适合提供了图形用户界面的应用程序,例如:windows 窗体、windows Presentation Foundation(WPF)、Silverlight、Windows Store 应用程序。它将所有任务都调度给应用程序的GUI 线程,使所有任务代码都能成功更新UI 组件。该任务调度器也不适用线程池。

下面的代码演示如何使用 同步上下文任务调度器:

internal sealed class MyForm : Form {
   private readonly TaskScheduler m_syncContextTaskScheduler;
   public MyForm() {
       // Get a reference to a synchronization context task scheduler
       m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
       Text = "Synchronization Context Task Scheduler Demo";
       Visible = true; Width = 600; Height = 100;
  }
   
   private CancellationTokenSource m_cts;
   
   protected override void OnMouseClick(MouseEventArgs e) {
       if (m_cts != null) { // An operation is in flight, cancel it
           m_cts.Cancel();
           m_cts = null;
      } else { // An operation is not in flight, start it
           Text = "Operation running";
           m_cts = new CancellationTokenSource();
           // 这个任务使用默认的任务调度器,在一个线程池线程上执行
           Task<Int32> t = Task.Run(() => Sum(m_cts.Token, 20000), m_cts.Token);
           // 这些任务使用同步上下文任务调度器,在GUI 线程中执行
           t.ContinueWith(task => Text = "Result: " + task.Result,
           CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
           m_syncContextTaskScheduler);
           t.ContinueWith(task => Text = "Operation canceled",
           CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled,
           m_syncContextTaskScheduler);
           t.ContinueWith(task => Text = "Operation faulted",
           CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted,
           m_syncContextTaskScheduler);
      }
  base.OnMouseClick(e);
  }
}

 

如果有特殊的任务需求,完全可以定义自己的TaskScheduler 派生类。

 

Parallel 的静态 For,ForEach,和 Invoke 方法

一些常见的编程情形可通过任务提升性能。为简化编程,静态 System.Threading.Tasks.Parallel 类封装了这些情形,它内部使用Task 对象。

不要像下面这样处理集合中的所有项,例如:

//一个线程顺序执行这个工作(每次迭代调用一次 Dowork)
for (Int32 i=0; i< 1000; i++) DoWork(i);

使用Parallel 类的For方法,用多个线程池线程辅助完成工作:

//线程池的线程并行处理工作
Parallel.For(0, 1000, i=>DoWork(i));

如果能用foreach 的地方可以这样写:

//线程池的线程并行处理工作
Parallel.ForEach(collection , item => DoWork(item));

能用For的地方尽量用for,因为它更快。

如果要执行多个方法,可以像下面这样执行:

Parallel.Invoke(
()=>method1();
()=>method2();
()=>method3();)

如果只为区区几个工作项使用Parallel的方法,或者为处理得非常快的工作项使用Parallel 的方法,就会得不偿失,反而降低性能。因为Parallel 的方法本身也有开销。

 

定时器的使用

System.Threading 命名空间定义了一个 Timer 类,可用它让一个线程池线程定时调用一个方法。

在内部,线程池为所有 Timer 对象只使用一个线程。这个线程知道下一个Timer 对象在什么时候到期(计时器还有多久触发)。下一个Timer 对象到期时,线程就会唤醒,在内部调用 ThreadPool 的 QueueUserWorkItem,将一个工作项添加到线程池的队列中,使你的回调方法得到调用。

如果回调方法的执行时间很长,计时器可能在上个回调还没完成的时候再次触发,这可能造成多个线程池线程同时执行你的回调方法。为解决这个问题,构造Timer 时,为period 参数指定 Timeout.Infinite。这样,计时器就只触发一次,然后,在你的回调方法中,调用Change 方法来指定一个新的dueTime,并再次为period 参数指定Timeout.Infinite。

Timer 类提供了一个 Dispose 方法,允许完全取消计时器,并可在当时出于pending 状态的所有回调完成之后,向notifyObject 参数标识的内核对象发出信号。

以下代码演示了如何让一个线程池线程立即回调方法,以后每2 秒调用一次:

internal static class TimerDemo {
   private static Timer s_timer;
   public static void Main() {
       Console.WriteLine("Checking status every 2 seconds");
       // Create the Timer ensuring that it never fires. This ensures that
       // s_timer refers to it BEFORE Status is invoked by a thread pool thread
       s_timer = new Timer(Status, null, Timeout.Infinite, Timeout.Infinite);
       // Now that s_timer is assigned to, we can let the timer fire knowing
       // that calling Change in Status will not throw a NullReferenceException
       s_timer.Change(0, Timeout.Infinite);
       Console.ReadLine(); // Prevent the process from terminating
  }
   // This method's signature must match the TimerCallback delegate
   private static void Status(Object state) {
       // This method is executed by a thread pool thread
       Console.WriteLine("In Status at {0}", DateTime.Now);
       Thread.Sleep(1000); // Simulates other work (1 second)
       // Just before returning, have the Timer fire again in 2 seconds
       s_timer.Change(2000, Timeout.Infinite);
       // When this method returns, the thread goes back
       // to the pool and waits for another work item
  }
}

如果有需要定时执行的操作,可利用Task 的静态Delay 方法和 C# 的async 和 await 关键字来编码。

internal static class DelayDemo {
   public static void Main() {
       Console.WriteLine("Checking status every 2 seconds");
       Status();
       Console.ReadLine(); // Prevent the process from terminating
  }
   // This method can take whatever parameters you desire
   private static async void Status() {
       while (true) {
           Console.WriteLine("Checking status at {0}", DateTime.Now);
           // Put code to check status here...
           // At end of loop, delay 2 seconds without blocking a thread
           await Task.Delay(2000); // await allows thread to return
           // After 2 seconds, some thread will continue after await to loop around
      }
  }
}

 

FCL 只提供几个计时器,下面介绍这几个计时器的特点:

  • System.Threading 的 Timer 类

    要在一个线程池线程上执行定时的后台任务,最好用它。

  • System.WIndows.Forms 的 Timer 类

    构造这个类的实例,相当于告诉WIndows 将一个计时器和调用线程关联。当这个计时器触发时,Windows 将一条计时器消息(WM_TIMER) 注入线程的消息队列。线程必须执行一个消息泵来提取这些消息,并把它们派发给需要的回调方法。注意,所有这些工作都只由一个线程完成 (设置计时器的线程保证就是执行回调方法的线程)。还意味着计时器方法不会由多个线程并发执行。

  • System.Windows.Threading 的 DispatcherTimer 类

    这个类是 System.Windows.Forms 的 Timer 类在 Silverlight 和 WPF 应用程序中的等价物。

  • Windows.UI.Xaml 的 DispatcherTimer 类

    这个类是System.Windows.Forms’s Timer 类 在Windows Store 应用中的等价物。

  • System.Timers’s Timer 类

    它本质上是 System.Threading 的 Timer 类的包装类。

 

线程池如何管理线程

CLR 团队将线程池默认 大约 1000 个线程。这基本上是不设限制。因为一个32位 进程最大有 2 GB 的可用空间。加载了一组 Win32 和CLR Dlls,并分配了本地堆和托管堆之后,剩余约 1.5 GB 的地址空间。由于每个线程都要为其用户模式栈 和 线程环境变量块(TEB)主备超过1 MB 的内存,所以在一个 32 位进程中,最多能有大约 1360 线程。

System.Threading.ThreadPool 类提供了几个静态方法,可调用它们设置和查询线程池线程的线程数:

GetMaxThreads、SetMaxThreads、GetMinThreads、SetMinThreads 和 GetAvailableThreads

Jeffrey Richter 强烈建议不要调用上述任何方法。Jeffrey 说设置线程池的线程数量会让程序的性能变得更差。我个人觉得线程池的数量不是应该保持在和CPU 数量的2倍上吗?

 

如何管理工作者线程

ThreadPool.QueueUserWorkItem 方法和 Timer 类总是将工作项放到全局队列中。工作者线程采用一个先入先出FIFO 算法将工作项从这个队列中取出,并处理它们。

由于是多个工作者线程在全局队列中拿走工作项,这就会形成并发情形,要有一个线程同步锁,保证两个或多个线程不会获取同一个工作项。这个线程同步锁在某些应用程序中可能成为瓶颈。

每个工作者线程都有自己的本地队列,工作者线程调度一个Task 时,该Task 被添加到调用线程的本地队列. 工作者线程采用先入后出 (LIFO)算法将任务从本地队列取出.

 

 

 

 工作者线程发现它的本地线程队列变空了 , 会尝试从另一个工作者线程的本地队列"偷" 一个Task , 这个Task 从本地队列的尾部 "偷走" , 并要求获取一个线程同步锁 .

如果所有本地队列都变空 , 工作者线程会从全局队列中取一个工作项 .

如果全局队列也为空 , 工作者线程会进入睡眠状态 , 等待事情的发生 .

如果睡眠时间太长了, 他会自己醒来, 销毁自身, 允许系统回收线程使用的资源( 内核对象, 栈, TEB 等)。

 

 

posted on 2019-10-24 20:18  拾掇的往昔  阅读(494)  评论(0编辑  收藏  举报

导航