iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
自我挑戰組

從C到JS的同步非同步探索系列 第 11 篇

[Day 11] .Net Task 底層(4)

  • 分享至 

  • xImage
  •  

前言

今天要聊到的是 Task 把超過自己承載能力的任務放入 TP 交給別條 thread 執行的過程發生了甚麼 ?

參考函數 : ThreadPool.UnsafeQueueCustomWorkItem

正文

翻看原始碼 ThreadPool.UnsafeQueueCustomWorkItem

[SecurityCritical]
internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal)
{
    Contract.Assert(null != workItem);
    EnsureVMInitialized();

    //
    // Enqueue needs to be protected from ThreadAbort
    //
    try { }
    finally
    {
        ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal);
    }
}

先來看參數

ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(singleTaskCompletionAction, this), forceGlobal: false);

CompletionActionInvoker 也就是原始碼的 IThreadPoolWorkItem workItem在 Task 的第一集就有提到, 創建時會把待完成任務存入, 並且留下介面供別人觸發此任務。

再來看看 ThreadPoolGlobals

internal static class ThreadPoolGlobals
{
    //Per-appDomain quantum (in ms) for which the thread keeps processing
    //requests in the current domain.
    public static uint tpQuantum = 30U;

    public static int processorCount = Environment.ProcessorCount;

    public static bool tpHosted = ThreadPool.IsThreadPoolHosted();

    public static volatile bool vmTpInitialized;
    public static bool enableWorkerTracking;

    [SecurityCritical]
    public static ThreadPoolWorkQueue workQueue = new ThreadPoolWorkQueue();

    [System.Security.SecuritySafeCritical] // static constructors should be safe to call
    static ThreadPoolGlobals()
    {
    }
}

發現這是一個 static 的變數, 其成員變數有一個 workQueue

而之前所說的, 把待完成任務放入 TP 由別條 thread 執行, 原來指的就是把任務 push 進 workQueue 這個資料結構。

此刻, 相信碰過 linux 的讀者會很快了聯想起 workQueue 這個資料結構所代表的意義, 所以我心急如焚的察看Enqueue method。

[SecurityCritical]
public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal)
{
		// forceGlobal=false ,獲取當前 thread 的任務列表
    ThreadPoolWorkQueueThreadLocals tl = null;
    if (!forceGlobal)
        tl = ThreadPoolWorkQueueThreadLocals.threadLocals;

    if (loggingEnabled)
        System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback);
		// 任務列表不為空, 加入新任務
    if (null != tl)
    {
        tl.workStealingQueue.LocalPush(callback);
    }
    else
    {
				// 任務列表為空, 加入新任務
        QueueSegment head = queueHead;

        while (!head.TryEnqueue(callback))
        {
            Interlocked.CompareExchange(ref head.Next, new QueueSegment(), null);

            while (head.Next != null)
            {
                Interlocked.CompareExchange(ref queueHead, head.Next, head);
                head = queueHead;
            }
        }
    }
		// 調用 worker thread 進行 schedule (此段程式碼利用 dll 引入)
    EnsureThreadRequested();
}

除了利用 CAS 方法 lock-free 的把存放進屬於 thread 的私人任務存放區外

果然找到了 , worker Thread 的調用, 以下簡單聊聊我對這段的理解。

小結

我會先說說何謂 linux 中的 workQueue , 因為在這段程式碼最後, 我們發現 .Net 向外引用了 worker thread 而這是外面的程式碼, 我看不到, 所以此刻只能用 workQueue來猜測其功能。接著說說我對 .Net ThreadPool的理解。

workQueue

以 linux 的workQueue為例(注:我程度不足,僅簡單說說)

workQueue創建時, 會創建 thread pool 作為 queue pool , 每個 queue 又可以放入多個任務, 所以呈現以下結構。

workQueue

  1. queue 1 ( thread 1 )
    1. task 1
    2. task 2
    3. task 3
  2. queue 2 ( thread 2 )
    1. NULL
  3. queue 3 ( thread 3 )
    1. task 4
  4. worker thread ( thread 4 )

發現裡面還多了一條 thread 名叫 worker thread , 其功能是排程 thread 1~3 , 包含 :

  1. 閒置沒有任務的 thread (ex thread 2 )
  2. 喚醒有任務的 thread 運行 等等。( ex thread 1,3 )

利用這樣的作法, 增進 multi-thread CPU 使用率, 不要有很多 thread 明明沒事做, 卻還在運作。

ThreadPool

以下從前天 Task 運行完成, 發現連續任務區有任務開始, 按照步驟陳述

  1. Task 進入完成階段
  2. Task 檢查連續任務區
  3. Task 發現連續任務區有任務
  4. Task 想直接接下去用同一條 thread 繼續執行, 但被禁止 ( 若沒禁止, 進入昨天的情況 )
  5. Task 調用 UnsafeQueueCustomWorkItem 把任務放入全域變數 ThreadPoolGlobals中的 workQueue
  6. 任務被放到 workQueue後被按照 thread 掛載到 thread 的任務區中
  7. 外來 method RequestWorkerThread 此時被呼叫, 會創建 workerThread 。
  8. workerThread 檢查各條 thread 的任務區, 使任務區為空的 thread 閒置, 喚醒任務區有任務的 thread 等等....。

至於被喚醒的 thread 內的任務如何被觸發, 會在之後說明。

到此 .Net thread pool 的探底算是告一段落, 可以發現, 若是用 linux 的 workQueue 的角度來理解, .Net 的 thread pool 就是實踐了上半部分的 workQueue , 包含維護各個 thread 的 queue, 並且把待執行的任務, 依情況放入各個 thread 的 queue 中。但是 .Net 本身不去排程這些 queue 和他們裡面的任務。

.Net 調用外來方法創建一條排程 thread , 這條排程 thread 會完成 workQueue 的排程任務, 包含

  1. 排程 ⇒ 調換各條 thread 的執行順序, 中斷卡住的 thread , 閒置沒有任務的 thread 等等......

注 : 關於外來排程 thread 的功能, 我僅能猜測, 因為原始碼不好找, 其調用了外部程式。有錯可以直接向我說~~

明天進度

透過這幾天的努力, 我們了解了 Task 執行完本身任務後處理連續任務區的任務的方法。

明天我們就開始來看看 Task 是怎麼執行本身的任務吧

明天見 !


上一篇
[Day 10] .Net Task 底層(3)
下一篇
[Day12] .Net Task 底層(5)
系列文
從C到JS的同步非同步探索 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言