C#_Process跟Thread深入研究探討(二)_Task使用總整


https://www.rover.com/blog/puppy-feeding-schedule-everything-need-know/

















融入生活的例子

某一天你的主管突然開始交付了
你無法負荷的工作量時此時又每天問候工作進度
開始就會有一些任務停擺因為你還有其他任務要做的問題發生

這個工作因為我在處理A需求因為比較緊急
所以暫時還沒處理

此時是不是考慮要雇用更多員工幫忙處理任務.........etc



Process 行程(進程)/處理(程)序
就是一個運行的程式,再白話一些你可以說任一exe開啟執行的都算是..

你任何一支無論用C# 、Java、VB.NET、C開發編譯出來的執行檔一被執行時
就會產生Process

你開的任何一個IE、Chrome瀏覽器或其他桌面端應用程式、Word、PowerPoint
也會產生一個Process


每個獨立的執行檔案於開啟時都會於作業系統
中被加載至內存並分配到一組資源來利用
此時我們就稱之為各個單獨的Process,相互不影響彼此。



提問:為何這麼設計??
主要也在於當一支程式掛掉時不會連動到其他程式的安全隔離用意。
雖然呀 ~~我們的應用程式和作業系統之間
已經透過Process的概念來達到隔離保護的效果。
但是必須要瞭解它們仍共用著同樣的CPU資源

因此一台股老機器若CPU只有一顆當某一支應用程式陷入無窮迴圈
則唯一的CPU就為變成只忙著跑該程式,進而無暇控管其餘程式的運行。
因此導致鎖死,因而沒有任何回應。

為了解決多個應用程式共用同一CPU的問題(CPU無法分身)
隨之所產生出Thread的概念。




Thread  執行緒(線程)
則是Process中的組成單元(基本執行單元)
在 Jeffrey Richter 先生所撰寫的 "CLR via C#" 一書中提到
「執行緒是Windows作業系統用來虛擬化CPU的概念」
更進一步解釋到
Windows OS 會配給每一個Process一個獨一無二
專屬的Thread(功能近似於縮小版本的CPU)
因此也可理解為執行緒就是所謂用來切割CPU執行時間之基本單位。


因此現在你啟動工作管理員所看到的個個運行的程式
會發現雖然只有一之程式但執行緒數量卻對應不只一個


於.NET程式語言中設計只要你的程式進入點是使用Main()方法
則會自動建立主執行緒(Main Thread)



Thread-Safe 執行緒安全
我們說 只包含一個 Main Thread的Process是Thread-safe的
主要是因為.NET程式中有規範
在某特定時刻允許只有一個Thread訪問程式中的資料
(在.NET規範中不允許跨執行緒訪問)




然而,如果 Thread 正在執行一些複雜的操作時(比方冗長的文本輸出、執行一個耗時的計算、資料庫讀取甚至嘗試遠程連線)此時一些使用者介面就會產生延遲、沒反應的問題。

也因為單執行緒存在此缺陷,使WindowsAPI和.NET程式有提供創建次執行緒相關函數。
這些次執行緒又常被稱作子執行緒、工作執行緒(worker thread)。
使原本Thread-safe的Process就像是開分身一般,演變成擁有多個Thread的Process,
此時我們稱為Non Thread-safe Process
(無論main thread還是sub thread皆是process當中的一個
獨立執行單元,並且可同時訪問及共享資料。)


總結:

單Thread程式
優點:
可確保資料數據讀寫異動比較不會出錯的安全性
缺點:
會導致當有多筆任務時只能依序完成,因為一個人(執行緒)無法一次多工,
因此會造成等待跟排隊。


多Thread程式
優點:
多分配更多分身(大家都各自是獨立的執行個體),幫忙分散
工作量,且可同一時間處理不同任務

缺點:
容易產生死結或是資料讀寫異動時的錯誤



提問:是不是Thread開越多越好??

在問這個問題的同時,要先瞭解到多thread情況下,電腦是由誰來安排任務又交給哪個thread來做的。

答案就是CPU,CPU就很像你的主管,會去花費時間觀察並確認每個員工(worker thread)的工作狀況和進度是否理想。

若我們把各個Thread比擬成部門中的員工,員工A可能工作負荷太大
那就要指派給另一個員工B來幫忙分擔,因此當有太多部屬要管理的時候
所要花費確認和轉換指派工作任務的時間也會提升。

隨著單一Process中的Thread過多,CPU所要花費的任務之間來回切換的耗時也會上升。
因此不是愈多愈好因為還有要考量到CPU的負荷。


對於單CPU電腦而言
其實並沒有能力去同一時間運行多個Thread。更確切一些來說,在任一時刻(時間片刻)內,單CPU只能透過Thread優先權來安排該時刻要執行哪一個Thread。
當一個Thread的執行時刻用完時,則會被掛在一邊作等待,以便執行其他Thread。


就有點像「獨木橋的概念」一次是無法容納得下多個人通過的,但是每次都會限定由誰來走過獨木橋而且有限定時刻(quantum)走完,每個人各自揹著要過去的貨就是所謂的執行任務量,每背過去一袋就完成一部分,之後再換另一個。

(備註:這裡提到的限定時刻這個短暫時間專有名詞為quantum!!!!)


https://www.flickr.com/photos/100mountain/27976254773



http://diranieh.com/NET/Assemblies.htm


對於單CPU下的Thread而言(可以想成身為工作者的自己)
需要在被掛在一旁前紀錄目前任務進度和發生捨麼事情,這些資訊會被寫入至執行緒本地儲存空間(Thread Local Storage/TLS),並且在要求獲得一個全新獨立的Call Stack

(關於Call Stack在其他語言中都有相同的概念,於之前js語言中我們有進行過探討,有興趣可參考此篇:Javascript背後運行原理(js引擎)_ExecutionStack)

https://docs.microsoft.com/en-us/windows/desktop/procthread/thread-local-storage


Context Switch (工作任務切換)
剛剛講到的過獨木橋概念因為有限定時刻運行因而有所切換
改背其他貨品過河

這裡可以想像成你在工作時一次會不只處理一件事情
因此你也會依照輕重緩急作相應切換一般的概念



時常混淆的名詞

並行(Concurrency):一次處理多件工作。
Ex:User邊輸入文字,應用程式於背後一邊檢查拼字正確與否。

多執行緒(multi-threading):特別用多執行緒方式實踐並行。

平行處理(parallel processing):將工作切分為多個細小的單位,分別交由多條執行緒同時處理。

非同步處理(asynchronous processing):也是一種並行形式,但不必(甚至避免)用執行緒,而常用「承諾」(promise) or 回忽事件(callback event)方式達到並行效果。






===============================================================


多Thread之建立共分為
1.無參數建構子委派之調用  ThreadStart()
2.無參數建構子委派之調用  ParameterizedThreadStart(Object obj)




兩種型態

CODE

1.無參數建構子委派之調用  ThreadStart()


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ThreadTest0
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(MyTask);
            t1.Start();

            for (int i = 0; i < 500; i++)
            {
                Console.Write(".");
            }


            Console.ReadKey();
        }

        /// <summary>
        /// 不帶有參數的Thread建構子函數 delegate
        /// delegate void ThreadStart();
        /// </summary>
        static void MyTask()
        {
            for(int i = 0; i < 500; i++)
            {
                Console.Write("["+ Thread.CurrentThread.ManagedThreadId +"]");
            }
        }
    }
}



2.無參數建構子委派之調用  ParameterizedThreadStart(Object obj)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ThreadTest0
{
    class Program
    {
        static void Main(string[] args)
        {
            //Thread t1 = new Thread(MyTask);
            //t1.Start();

            //for (int i = 0; i < 500; i++)
            //{
            //    Console.Write(".");
            //}

            Thread th1 = new Thread(MyTaskWithParam);
            Thread th2 = new Thread(MyTaskWithParam);
            Thread th3 = new Thread(MyTaskWithParam);

            th1.Start("x");
            th2.Start("y");
            th3.Start("z");

            for (int i = 0; i < 500; i++)
            {
                Console.Write(".");
            }


            Console.ReadKey();
        }

        /// <summary>
        /// 不帶有參數的Thread建構子函數 delegate
        /// delegate void ThreadStart();
        /// </summary>
        static void MyTask()
        {
            for(int i = 0; i < 500; i++)
            {
                Console.Write("["+ Thread.CurrentThread.ManagedThreadId +"]");
            }
        }

        /// <summary>
        /// 帶有參數的Thread建構子函數 delegate
        /// delegate void ParameterizedThreadStart(Object obj);
        /// </summary>
        /// <param name="param"></param>
        static void MyTaskWithParam(Object param)
        {
            for (int i = 0; i < 500; i++)
            {
                Console.Write(param);
            }
        }
    }
}



由上述幾個是Thread之間各自切換執行的過程觀察。

若我們希望  第二個示範中所開的三個Thread可以依序同步執行

則牽涉到Thread的等待
於C#中我們會透過Join語法來讓主Thread依序等待各個worker thread執行完再往下執行



主Thread依序等待各個worker thread執行完再往下執行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ThreadTest0
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread th1 = new Thread(MyTaskWithParam);
            Thread th2 = new Thread(MyTaskWithParam);
            Thread th3 = new Thread(MyTaskWithParam);

            th1.Start("x");
            th2.Start("y");
            th3.Start("z");

            th1.Join();
            th2.Join();
            th3.Join();


            Console.ReadKey();
        }

        static void MyTaskWithParam(Object param)
        {
            Console.WriteLine("{0}開始在執行", param);
            Thread.Sleep(3000);
            Console.WriteLine("{0}完成工作", param);
        }
    }
}




理想情況:
各執行緒相互不影響各自分頭運行,對於設計上比較單純。



現實實務面:
我們很常要在一些特定情況下,
遇到需要讓多個Thread去共享並處理變數內容的不單純情況,因而有所麻煩。
尤其在同時有多個Thread去修改時就需要有相應較巧防止錯誤

https://www.pinterest.com/pin/100205160442989975/



自.NET4.0 之後有提供 TPL (Task Parallel Library)的API
特色在於把異步程式設計給模組化成統一單元。
使我們可以直接透過Task調用多個異步運算的程式運行流程( asynchronous operations)

對於 Task 一詞則可視為「異步工作單元」



Creating a compute-based task

如下我們來嘗試建立一個用於運算的Task
預設我們要塞入的參數型態為一Action delegate



















留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題