欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

.NET?Core?線程池(ThreadPool)底層原理源碼解析

 更新時間:2024年11月26日 09:12:38   作者:叫我安不理  
文章介紹了.NET?Core線程池的結(jié)構(gòu)和工作原理,包括生產(chǎn)者-消費者模型、線程饑餓問題、線程池增長策略等,同時,對比了Task和線程池在并發(fā)編程中的優(yōu)缺點,并推薦使用Task來優(yōu)化線程池的使用,感興趣的朋友一起看看吧

簡介

上文提到,創(chuàng)建線程在操作系統(tǒng)層面有4大無法避免的開銷。因此復用線程明顯是一個更優(yōu)的策略,切降低了使用線程的門檻,提高程序員的下限。

.NET Core線程池日新月異,不同版本實現(xiàn)都有差別,在.NET 6之前,ThreadPool底層由C++承載。在之后由C#承載。本文以.NET 8.0.8為藍本,如有出入,請參考源碼.

ThreadPool結(jié)構(gòu)模型圖

眼見為實

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPoolWorkQueue.cs上源碼 and windbg

internal sealed partial class ThreadPoolWorkQueue
{
        internal readonly ConcurrentQueue<object> workItems = new ConcurrentQueue<object>();//全局隊列
        internal readonly ConcurrentQueue<object> highPriorityWorkItems = new ConcurrentQueue<object>();//高優(yōu)先級隊列,比如Timer產(chǎn)生的定時任務
        internal readonly ConcurrentQueue<object> lowPriorityWorkItems =
            s_prioritizationExperiment ? new ConcurrentQueue<object>() : null!;//低優(yōu)先級隊列,比如回調(diào)
        internal readonly ConcurrentQueue<object>[] _assignableWorkItemQueues =
            new ConcurrentQueue<object>[s_assignableWorkItemQueueCount];//CPU 核心大于32個,全局隊列會分裂為好幾個,目的是降低CPU核心對全局隊列的鎖競爭
}

ThreadPool生產(chǎn)者模型

眼見為實

        public void Enqueue(object callback, bool forceGlobal)
        {
            Debug.Assert((callback is IThreadPoolWorkItem) ^ (callback is Task));
            if (_loggingEnabled && FrameworkEventSource.Log.IsEnabled())
                FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
#if CORECLR
            if (s_prioritizationExperiment)//lowPriorityWorkItems目前還是實驗階段,CLR代碼比較偷懶,這一段代碼很不優(yōu)雅,沒有連續(xù)性。
            {
                EnqueueForPrioritizationExperiment(callback, forceGlobal);
            }
            else
#endif
            {
                ThreadPoolWorkQueueThreadLocals? tl;
                if (!forceGlobal && (tl = ThreadPoolWorkQueueThreadLocals.threadLocals) != null)
                {
                    tl.workStealingQueue.LocalPush(callback);//如果沒有特殊情況,默認加入本地隊列
                }
                else
                {
                    ConcurrentQueue<object> queue =
                        s_assignableWorkItemQueueCount > 0 && (tl = ThreadPoolWorkQueueThreadLocals.threadLocals) != null
                            ? tl.assignedGlobalWorkItemQueue//CPU>32 加入分裂的全局隊列
                            : workItems;//CPU<=32 加入全局隊列
                    queue.Enqueue(callback);
                }
            }
            EnsureThreadRequested();
        }

細心的朋友,會發(fā)現(xiàn)highPriorityWorkItems的注入判斷哪里去了?目前CLR對于高優(yōu)先級隊列只開放給內(nèi)部,比如timer/Task使用

ThreadPool消費者模型

眼見為實

public object? Dequeue(ThreadPoolWorkQueueThreadLocals tl, ref bool missedSteal)
        {
            // Check for local work items
            object? workItem = tl.workStealingQueue.LocalPop();
            if (workItem != null)
            {
                return workItem;
            }
            // Check for high-priority work items
            if (tl.isProcessingHighPriorityWorkItems)
            {
                if (highPriorityWorkItems.TryDequeue(out workItem))
                {
                    return workItem;
                }
                tl.isProcessingHighPriorityWorkItems = false;
            }
            else if (
                _mayHaveHighPriorityWorkItems != 0 &&
                Interlocked.CompareExchange(ref _mayHaveHighPriorityWorkItems, 0, 1) != 0 &&
                TryStartProcessingHighPriorityWorkItemsAndDequeue(tl, out workItem))
            {
                return workItem;
            }
            // Check for work items from the assigned global queue
            if (s_assignableWorkItemQueueCount > 0 && tl.assignedGlobalWorkItemQueue.TryDequeue(out workItem))
            {
                return workItem;
            }
            // Check for work items from the global queue
            if (workItems.TryDequeue(out workItem))
            {
                return workItem;
            }
            // Check for work items in other assignable global queues
            uint randomValue = tl.random.NextUInt32();
            if (s_assignableWorkItemQueueCount > 0)
            {
                int queueIndex = tl.queueIndex;
                int c = s_assignableWorkItemQueueCount;
                int maxIndex = c - 1;
                for (int i = (int)(randomValue % (uint)c); c > 0; i = i < maxIndex ? i + 1 : 0, c--)
                {
                    if (i != queueIndex && _assignableWorkItemQueues[i].TryDequeue(out workItem))
                    {
                        return workItem;
                    }
                }
            }
#if CORECLR
            // Check for low-priority work items
            if (s_prioritizationExperiment && lowPriorityWorkItems.TryDequeue(out workItem))
            {
                return workItem;
            }
#endif
            // Try to steal from other threads' local work items
            {
                WorkStealingQueue localWsq = tl.workStealingQueue;
                WorkStealingQueue[] queues = WorkStealingQueueList.Queues;
                int c = queues.Length;
                Debug.Assert(c > 0, "There must at least be a queue for this thread.");
                int maxIndex = c - 1;
                for (int i = (int)(randomValue % (uint)c); c > 0; i = i < maxIndex ? i + 1 : 0, c--)
                {
                    WorkStealingQueue otherQueue = queues[i];
                    if (otherQueue != localWsq && otherQueue.CanSteal)
                    {
                        workItem = otherQueue.TrySteal(ref missedSteal);
                        if (workItem != null)
                        {
                            return workItem;
                        }
                    }
                }
            }
            return null;
        }

什么是線程饑餓?

線程饑餓(Thread Starvation)是指線程長時間得不到調(diào)度(時間片),從而無法完成任務。

  • 線程被無限阻塞
    當某個線程獲取鎖后長期不釋放,其它線程一直在等待
  • 線程優(yōu)先級降低
    操作系統(tǒng)鎖競爭中,高優(yōu)先級線程,搶占低優(yōu)先級線程的CPU時間
  • 線程在等待
    比如線程Wait/Result時,線程池資源不夠,導致得不到執(zhí)行

眼見為實

@一線碼農(nóng) 使用大佬的案例

http://www.dbjr.com.cn/program/3313770o1.htm

http://www.dbjr.com.cn/aspnet/3313810g7.htm

windbg sos bug依舊存在

大佬的文章中,描述sos存在bug,無法顯示線程堆積情況

經(jīng)實測,在.net 8中依舊存在此bug

99851個積壓隊列,沒有顯示出來

ThreadPool如何改善線程饑餓

CLR線程池使用爬山算法來動態(tài)調(diào)整線程池的大小來來改善線程饑餓的問題。本人水平有限,放出地址,有興趣的同學可以自行研究https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.HillClimbing.cs

ThreadPool如何增加線程

在 PortableThreadPool 中有一個子類叫 GateThread,它就是專門用來增減線程的類

其底層使用一個while (true) 每隔500ms來輪詢線程數(shù)量是否足夠,以及一個AutoResetEvent來接收注入線程Event.如果不夠就新增

《CLR vir C#》 一書中,提過一句 CLR線程池每秒最多新增1~2個線程。結(jié)論的源頭就是在這里注意:是線程池注入線程每秒1~2個,不是每秒只能創(chuàng)建1~2個線程。OS創(chuàng)建線程的速度塊多了。

眼見為實

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs

眼見為實

        static void Main(string[] args)
        {
            for (int i = 0;i<=100000;i++)
            {
                ThreadPool.QueueUserWorkItem((x) =>
                {
                    Console.WriteLine($"當前線程Id:{Thread.CurrentThread.ManagedThreadId}");
                    Thread.Sleep(int.MaxValue);
                });
            }
            Console.ReadLine();
        }

可以觀察輸出,判斷是不是每秒注入1~2個線程

Task

不用多說什么了吧?

Task的底層調(diào)用模型圖

Task的底層實現(xiàn)主要取決于TaskSchedule,一般來說,除了UI線程外,默認是調(diào)度到線程池

眼見為實

Task.Run(() => { { Console.WriteLine("Test"); } });

其底層會自動調(diào)用Start(),Start()底層調(diào)用的TaskShedule.QueueTask().而作為實現(xiàn)類ThreadPoolTaskScheduler.QueueTask底層調(diào)用如下。

可以看到,默認情況下(除非你自己實現(xiàn)一個TaskShedule抽象類).Task的底層使用ThreadPool來管理。

有意思的是,對于長任務(Long Task),直接是用一個單獨的后臺線程來管理,完全不參與調(diào)度。

Task對線程池的優(yōu)化

既然Task的底層是使用ThreadPool,而線程池注入速度是比較慢的。Task作為線程池的高度封裝,有沒有優(yōu)化呢?答案是Yes當使用Task.Result時,底層會調(diào)用InternalWaitCore(),如果Task還未完成,會調(diào)用ThreadPool.NotifyThreadBlocked()來通知ThreadPool當前線程已經(jīng)被阻塞,必須馬上注入一個新線程來代替被阻塞的線程。相對每500ms來輪詢注入線程,該方式采用事件驅(qū)動,注入線程池的速度會更快。

眼見為實

  static void Main(string[] args)
        {
            var client = new HttpClient();
            for(int i = 0; i < 100000; i++)
            {
                ThreadPool.QueueUserWorkItem(x =>
                {
                    Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff")} -> {x}: 這是耗時任務");
                    try
                    {
                        var content = client.GetStringAsync("https://youtube.com").Result;
                        Console.WriteLine(content);
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                });
            }
            Console.ReadLine();
        }

其底層通過AutoResetEvent來觸發(fā)注入線程的Event消息

結(jié)論

多用Task,它更完善。對線程池優(yōu)化更好。沒有不使用Task的理由

到此這篇關于.NET Core 線程池(ThreadPool)底層原理淺談的文章就介紹到這了,更多相關.NET Core 線程池ThreadPool內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論