日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C#并行编程(2):.NET线程池

發布時間:2023/12/4 C# 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#并行编程(2):.NET线程池 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

線程?Thread

在總結線程池之前,先來看一下.NET線程。

.NET線程與操作系統(Windows)線程有什么區別?

.NET利用Windows的線程處理功能。在C#程序編寫中,我們首先會新建一個線程對象System.Threading.Thread,并為其指定一個回調方法;當我們調用線程對象的Start方法啟動線程時,會創建一個操作系統線程來執行回調方法。.NET中的線程實際上等價于Windows系統線程,都是CPU調度和分配的對象。

前臺線程和后臺線程

.NET把線程分為前臺線程和后臺線程,兩者幾乎相同,唯一的區別是,前臺線程會阻止進程的正常退出,后臺線程則不會。下面用一個例子描述前、后臺線程的區別:


class Program
{
static void Main(string[] args)
{
ThreadDemo threadDemo = new ThreadDemo();

threadDemo.RunBackgroundThread();

{
Thread.Sleep(5000);
Thread.CurrentThread.Abort();
}

Console.ReadKey();
}
}

public class ThreadDemo
{
private readonly Thread _foregroundThread;
private readonly Thread _backgroundThread;

public ThreadDemo()
{
this._foregroundThread = new Thread(WriteNumberWorker) { Name = "ForegroundThread"};
this._backgroundThread = new Thread(WriteNumberWorker) { Name = "BackgroundThread", IsBackground = true };
}




private static void WriteNumberWorker()
{
for (int i = 0; i < 20; i++)
{
Console.WriteLine($"{DateTime.Now}=> {Thread.CurrentThread.Name} writes {i + 1}.");
Thread.Sleep(500);
}
}




public void RunForegroundThread()
{
this._foregroundThread?.Start();
}




public void RunBackgroundThread()
{
this._backgroundThread?.Start();
}
}

線程池?ThreadPool

線程的創建和銷毀要耗費很多時間,而且過多的線程不僅會浪費內存空間,還會導致線程上下文切換頻繁,影響程序性能。為改善這些問題,.NET運行時(CLR)會為每個進程開辟一個全局唯一的線程池來管理其線程。

線程池內部維護一個操作請求隊列,程序執行異步操作時,添加目標操作到線程池的請求隊列;線程池代碼提取記錄項并派發給線程池中的一個線程;如果線程池中沒有可用線程,就創建一個新線程,創建的新線程不會隨任務的完成而銷毀,這樣就可以避免線程的頻繁創建和銷毀。如果線程池中大量線程長時間無所事事,空閑線程會進行自我終結以釋放資源。

線程池通過保持進程中線程的少量和高效來優化程序的性能。

C#中線程池是一個靜態類,維護兩種線程,工作線程和異步IO線程,這些線程都是后臺線程。線程池不會影響進程的正常退出。

線程池的使用

線程池提供兩個靜態方法SetMaxThreads和SetMinThreads讓我們設置線程池的最大線程數和最小線程數。最大線程數指的是,該線程池能夠創建的最大線程數,當線程數達到設定值且忙碌,異步任務將進入請求隊列,直到有線程空閑才會執行;最小線程數指的是,線程池優先嘗試以設置數量的線程處理請求,當請求數達到一定量(未做深入研究)時,才會創建新的線程。

下面的例子展示了線程池的特性及常見使用方式。

class Program
{
static void Main(string[] args)
{

RunCancellableWork();

Console.ReadKey();
}

static void RunThreadPoolDemo()
{
ThreadPoolDemo.ThreadPoolDemo.ShowThreadPoolInfo();
ThreadPool.SetMaxThreads(100, 100);
ThreadPool.SetMinThreads(8, 8);
ThreadPoolDemo.ThreadPoolDemo.ShowThreadPoolInfo();
ThreadPoolDemo.ThreadPoolDemo.MakeThreadPoolDoSomeWork(100);
ThreadPoolDemo.ThreadPoolDemo.MakeThreadPoolDoSomeIOWork();
}

static void RunCancellableWork()
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] started a work");
Console.WriteLine("Press 'Esc' to cancel the work.");
Console.WriteLine();
ThreadPoolDemo.ThreadPoolDemo.DoSomeWorkWithCancellation();
if (Console.ReadKey(true).Key == ConsoleKey.Escape)
{
ThreadPoolDemo.ThreadPoolDemo.CTSource.Cancel();
}
}
}

public class ThreadPoolDemo
{



public static void ShowThreadPoolInfo()
{
int workThreads, completionPortThreads;


ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetAvailableThreads?=>?workThreads:{workThreads};completionPortThreads:{completionPortThreads}");

ThreadPool.GetMaxThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetMaxThreads?=>?workThreads:{workThreads};completionPortThreads:{completionPortThreads}");

ThreadPool.GetMinThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"GetMinThreads?=>?workThreads:{workThreads};completionPortThreads:{completionPortThreads}");
Console.WriteLine();
}




public static void MakeThreadPoolDoSomeWork(int workCount = 10)
{
for (int i = 0; i < workCount; i++)
{
int index = i;

ThreadPool.QueueUserWorkItem(s =>
{
Thread.Sleep(100);
Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{index}]");
ShowAvailableThreads("WorkerThread");
});
}
}




public static void MakeThreadPoolDoSomeIOWork()
{

IList<string> uriList = new List<string>()
{
"http://news.baidu.com/",
"https://www.hao123.com/",
"https://map.baidu.com/",
"https://tieba.baidu.com/",
"https://wenku.baidu.com/",
"http://fanyi-pro.baidu.com",
"http://bit.baidu.com/",
"http://xueshu.baidu.com/",
"http://www.cnki.net/",
"http://www.wanfangdata.com.cn",
};

foreach (string uri in uriList)
{
WebRequest request = WebRequest.Create(uri);
request.BeginGetResponse(ac =>
{
try
{
WebResponse response = request.EndGetResponse(ac);
ShowAvailableThreads("IOThread");
Debug.Print($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] is running. [{response.ContentLength}]");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}, request);
}
}




private static void ShowAvailableThreads(string sourceTag = null)
{
int workThreads, completionPortThreads;
ThreadPool.GetAvailableThreads(out workThreads, out completionPortThreads);
Console.WriteLine($"{sourceTag}?GetAvailableThreads?=>?workThreads:{workThreads};completionPortThreads:{completionPortThreads}");
Console.WriteLine();
}




public static CancellationTokenSource CTSource { get; set; } = new CancellationTokenSource();




public static void DoSomeWorkWithCancellation()
{
ThreadPool.QueueUserWorkItem(t =>
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] begun running. [0 - 9999]");

for (int i = 0; i < 10000; i++)
{
if (CTSource.Token.IsCancellationRequested)
{
Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] recived the cancel token. [{i}]");
break;
}
Thread.Sleep(100);
}

Console.WriteLine($"{DateTime.Now}=> Thread-[{Thread.CurrentThread.ManagedThreadId}] was cancelled.");
});
}
}

線程池的調度

前面提到,線程池內部維護者一個工作項隊列,這個隊列指的是線程池全局隊列。實際上,除了全局隊列,線程池會給每個工作者線程維護一個本地隊列。

當我們調用ThreadPool.QueueUserWorkItem方法時,工作項會被放入全局隊列;使用定時器Timer的時候,也會將工作項放入全局隊列;但是,當我們使用任務Task的時候,假如使用默認的任務調度器,任務會被調度到工作者線程的本地隊列中。

工作者線程優先執行本地隊列中最新進入的任務,如果本地隊列中已經沒有任務,線程會嘗試從其他工作者線程任務隊列的隊尾取任務執行,這里需要進行同步。如果所有工作者線程的本地隊列都沒有任務可以執行,工作者線程才會從全局隊列取最新的工作項來執行。所有任務執行完畢后,線程睡眠,睡眠一定時間后,線程醒來并銷毀自己以釋放資源。

線程池處理異步IO的內部原理

上面的例子中,從網站獲取信息需要用到線程池的異步IO線程,線程池內部利用IOCP(IO完成端口)與硬件設備建立連接。異步IO實現過程如下:

  • 托管的IO請求線程調用Win32本地代碼ReadFile方法

  • ReadFile方法分配IO請求包IRP并發送至Windows內核

  • Windows內核把收到的IRP放入對應設備驅動程序的IRP隊列中,此時IO請求線程已經可以返回托管代碼

  • 驅動程序處理IRP并將處理結果放入.NET線程池的IRP結果隊列中

  • 線程池分配IO線程處理IRP結果

  • 小結

    .NET線程池是并發編程的主要實現方式。C#中Timer、Parallel、Task在內部都是利用線程池實現的異步功能,深入理解線程池在并行編程中十分重要。

    原文地址:https://www.cnblogs.com/chenbaoshun/p/10566124.html

    .NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?

    總結

    以上是生活随笔為你收集整理的C#并行编程(2):.NET线程池的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。