【C# 線程】Thread類 以及使用案例


 System.Threading.Thread類

涉及到的類和枚舉

Volatile 類
Interlocked 類
SpinLock 類
SpinWait類
Barrier 類
ThreadLocal<T> 類
ApartmentState 枚舉
ThreadPriority 枚舉
ThreadStart 類
ThreadStartException
ThreadState 枚舉

 

構造函數

 名稱                                                說明
Thread(ParameterizedThreadStart)     初始化 Thread 類的新實例,指定允許對象在線程啟動時傳遞給線程的委托。要執行的方法是有參的。

                                                              public delegate void ParameterizedThreadStart(object? obj)


Thread(ParameterizedThreadStart, Int32)     初始化 Thread 類的新實例,指定允許對象在線程啟動時傳遞給線程的委托,並指定線程的最大堆棧大小
Thread(ThreadStart)     初始化 Thread 類的新實例。要執行的方法是無參的。
Thread(ThreadStart, Int32)     初始化 Thread 類的新實例,指定線程的最大堆棧大小。

 

屬性

屬性名稱     說明

ApartmentState 屬性已過時。 未過時的替代 GetApartmentState 方法是檢索單元狀態的方法,以及 SetApartmentState 用於設置單元狀態的方法。
CurrentContext     獲取線程正在其中執行的當前上下文。
CurrentThread     獲取當前正在運行的線程。
ExecutionContext     獲取一個 ExecutionContext 對象,該對象包含有關當前線程的各種上下文的信息。
IsAlive     獲取一個值,該值指示當前線程的執行狀態。
IsBackground     獲取或設置一個值,該值指示某個線程是否為后台線程。
IsThreadPoolThread     獲取一個值,該值指示線程是否屬於托管線程池。
ManagedThreadId     獲取當前托管線程的唯一標識符。
Name     獲取或設置線程的名稱。
Priority     獲取或設置一個值,該值指示線程的調度優先級。
ThreadState     獲取一個值,該值包含當前線程的狀態。

 

使用案例

Thread thread = new Thread(SleepAwait);
Thread thread2 = new Thread(SleepAwait2);

thread.Name = " thread";
thread.Start();

thread2.Name = " thread2";
thread2.Priority=ThreadPriority.BelowNormal;
thread2.Start(6000);

Console.WriteLine(thread2.Name);
Console.WriteLine("thread2.ManagedThreadId:{0}",thread2.ManagedThreadId);
Console.WriteLine("thread2.ThreadState:{0}", thread2.ThreadState);
Console.WriteLine("thread2.GetApartmentState:{0}", thread2.GetApartmentState());
Console.WriteLine("thread2.IsAlive:{0}", thread2.IsAlive);
Console.WriteLine("thread2.Priority:{0}", thread2.Priority);
Console.WriteLine("thread2.IsBackground:{0}", thread2.IsBackground);
Console.WriteLine("thread2.IsThreadPoolThread:{0}", thread2.IsThreadPoolThread);
Console.WriteLine("thread2.CurrentCulture:{0}", thread2.CurrentCulture);
Console.WriteLine("thread2.CurrentUICulture:{0}", thread2.CurrentUICulture);
 
void SleepAwait2(object? obj)
{
    Console.WriteLine(Thread.CurrentThread.Name);
    Console.WriteLine(Thread.CurrentThread.CurrentUICulture.ToString());
    int time=4000;
    if (obj != null)
    {
       time = (int)obj;
    }


    Thread.Sleep(time);
    Console.WriteLine($"I won to sleep {time} second ");
}

static void SleepAwait()
{
    Console.WriteLine(Thread.CurrentThread.Name);
    Console.WriteLine(Thread.CurrentThread.CurrentUICulture.ToString());
    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Console.WriteLine(Thread.CurrentThread.ThreadState);
    Console.WriteLine(Thread.CurrentThread.GetApartmentState());
    Console.WriteLine(Thread.CurrentThread.IsAlive);
    Console.WriteLine("I won to sleep 5000 second ");
    Console.WriteLine(Thread.CurrentThread.Priority);

    Thread.Sleep(1000);

}

 

 

方法

 =======================cpu控制============================================

Thread.Yeild()方法
讓有需要的人先用
Yield 的中文翻譯為 “屈服,讓步”,這里意思是主動放棄當前線程的時間片,並讓操作系統調度其它就緒態的線程使用一個時間片。但是如果調用 Yield,只是把當前線程放入到就緒隊列中,而不是阻塞隊列。如果沒有找到其它就緒態的線程,則當前線程繼續運行。Yield可以讓低於當前優先級的線程得以運行,調用者可以通過返回值判斷是否成功調度了其它線程。注意,Yield只能調度運行在當前CPU上的線程,如果有多個cpu 無法讓步給其他cpu上的線程。
如果操作系統轉而執行另一個線程,則為 true;否則為 false。此方法等效於使用平台調用來調用本機 Win32 SwitchToThread 函數。 應調用 Yield 方法,而不是使用平台調用

案例:

  while (true)
            {
                // 主動讓出CPU,如果有其他就緒線程就執行其他線程,如果沒有則繼續當前線程
                var result = Thread.Yield();

                // Do Some Work
                Console.WriteLine("Thread 1 running, yield result {0}", result);
            }

 

Thread.Sleep(0):讓級別高的線程先執行(讓領導先用),如果等待線程中,沒有優先級更高的,該線程就繼續執行。
Thread.Sleep(1),進入就緒隊列。該方法使用 1 作為參數,這會強制當前線程放棄剩下的時間片,並休息 1 毫秒(因為不是實時操作系統,時間無法保證精確,一般可能會滯后幾毫秒或一個時間片)。但因此的好處是,所有其它就緒狀態的線程都有機會競爭時間片,而不用在乎優先級。
Sleep(N)休息一伙

 

 

讓領導先走
在線程沒退出之前,線程有三個狀態,就緒態,運行態,等待態。sleep(n)之所以在n秒內不會參與CPU競爭,是因為,當線程調用sleep(n)的時候,線程是由運行態轉入等待態,線程被放入等待隊列中,等待定時器n秒后的中斷事件,當到達n秒計時后,線程才重新由等待態轉入就緒態,被放入就緒隊列中,等待隊列中的線程是不參與cpu競爭的,只有就緒隊列中的線程才會參與cpu競爭,所謂的cpu調度,就是根據一定的算法(優先級,FIFO等。。。),從就緒隊列中選擇一個線程來分配cpu時間。
而sleep(0)之所以馬上回去參與cpu競爭,是因為調用sleep(0)后,因為0的原因,線程直接回到就緒隊列,而非進入等待隊列,只要進入就緒隊列,那么它就參與cpu競爭。
 

 

SpinWait():

SpinWait本質上是將處理器放入一個非常緊湊的循環中,循環計數由迭代參數指定。因此,等待的時間長短取決於處理器的速度。說白了SpinWait就是一個for循環,我們只要輸入一個數字就行。總的循環的時間由處理器的處理速度決定。
SpinWait對於普通應用程序通常不是很有用。在大多數情況下,你應該使用。net框架提供的同步類;例如:調用Monitor
Thread.SpinWait 案例

using System.Diagnostics;
 

class Program
{

    static SpinLock sl = new();
   
    static void Main(string[] args)
    {
 
        Stopwatch stopwatch = new ();
        SpinWait sw = new();

        for (int i = 0; i < 20; i++)
        {
            int ss = 1 << i;//1向左移動i位
            stopwatch.Reset();
            stopwatch.Start();
         
            Thread.SpinWait(ss);
            stopwatch.Stop();
            
            Console.WriteLine(stopwatch.ElapsedTicks+sw.NextSpinWillYield.ToString()+"count:"+ ss);
        }


    }
}

 

BeginThreadAffinity:目前是空方法,以前是該方法調用IHostTaskManager 接口通知宿主托管代碼正在輸入一個時間段,在此期間,不能將當前任務移動到另一個線程。。主要用到的是線程的親和調度算法Affinity
EndThreadAffinity:線程不再需要使用物理操作系統線程運行時,可調用Thread的EndThreadAffinity方法來通知CLR。
BeginCriticalRegion:線程中訪問臨界資源的那段代碼。通知宿主執行將要進入一個代碼區域,在該代碼區域內線程中止或未處理的異常的影響可能會危害應用程序域中的其他任務。
EndCriticalRegion:通知宿主執行將要進入一個代碼區域,在該代碼區域內線程中止或未處理的異常僅影響當前任務。

 

==================================數據曹的用法==============================================

AllocateDataSlot    申請匿名的數據曹
AllocateNamedDataSlot    申請命名的數據曹
GetNamedDataSlot(name)    釋放name數據曹
FreeNamedDataSlot(name)    釋放name數據曹,
GetApartmentState:設置線程狀態 MTA或者STA
SetApartmentState:設置線程狀態 MTA或者STA
TrySetApartmentState
GetData    獲取據曹的值
SetData    給數據曹賦值

這些函數用法:

哪個線程設定的數據曹,就那個線程使用(誰設定誰使用),其他線程無權讀取和設定。

using System;
using System.Threading;
class Programe
{    static void Main()
    {
    
        Foo foo = new Foo();
        Thread thread = new Thread(new ThreadStart(foo.A));
        thread.SetApartmentState(ApartmentState.STA);
        //設置數據曹
        LocalDataStoreSlot dataslot = Thread.AllocateNamedDataSlot("maindata");
        Thread.SetData(dataslot, "mainThreadDataslot");
        
       //獲取數據曹
        LocalDataStoreSlot dataslot1 = Thread.GetNamedDataSlot("maindata");
        Console.WriteLine("主線程數據曹{0}:",(string)Thread.GetData(dataslot1) );

        thread.Start();
        Console.Read();
    }
}
class Foo
{
  public   void A()
    {
        LocalDataStoreSlot dataslot = Thread.GetNamedDataSlot("maindata");
        if (dataslot is null) return;
        string see = (string)Thread.GetData(dataslot) ;
        Console.WriteLine("Thread{0}讀取MainThread的數據曹:{1}", Environment.CurrentManagedThreadId, see);
        Console.WriteLine(Thread.CurrentThread.GetApartmentState());
    }
    
}

 

 

===========================內存屏障=====================================

 

MemoryBarrier:內存屏障  相當於分隔符,內部調用了Interlocked.MemoryBarrier() 作用主要兩個:
      1、阻隔代碼流動:編譯器或clr cpu不能將Thread.MemoryBarrier() 前面代碼,移動到他后面,也不允許它后面的代碼 移到它前面。它就像一堵牆隔離了代碼優化帶來的代碼移動。
      2、緩沖和寄存器數據的新老划段:Thread.MemoryBarrier() 后面的代碼用的的變量值,只能從內存中重新提取,不允許使用Thread.MemoryBarrier() 前面代碼放在緩存或寄存器中的值"

本文簡單的介紹一下這兩個概念,假設下面的代碼:

using System;
class Foo
{
    int _answer;
    bool _complete;
 
    void A()
    {
        _answer = 123;
        _complete = true;
    }
 
    void B()
    {
        if (_complete) Console.WriteLine(_answer);
    }
}

如果方法A和方法B同時在兩個不同線程中運行,控制台可能輸出0嗎?答案是可能的,有以下兩個原因:

  • 編譯器,CLR或者CPU可能會更改指令的順序來提高性能
  • 編譯器,CLR或者CPU可能會通過緩存來優化變量,這種情況下對其他線程是不可見的。

最簡單的方式就是通過MemoryBarrier來保護變量,來防止任何形式的更改指令順序或者緩存。調用Thread.MemoryBarrier會生成一個內存柵欄,我們可以通過以下的方式解決上面的問題:

using System;
using System.Threading;
class Foo
{
    int _answer;
    bool _complete;
 
    void A()
    {
        _answer = 123;
        Thread.MemoryBarrier();    // Barrier 1
        _complete = true;
        Thread.MemoryBarrier();    // Barrier 2
    }
 
    void B()
    {
        Thread.MemoryBarrier();    // Barrier 3
        if (_complete)
        {
            Thread.MemoryBarrier();       // Barrier 4
            Console.WriteLine(_answer);
        }
    }
}


上面的例子中,barrier1和barrier2用來保證指令順序不會改變和不緩存數據,直接將數據寫入內存,barrier3和barrier4用來 直接讀取內存數,保證數值式最新的。

 

 


VolatileRead:原子鎖,立即將字段的數值寫入內存中,內部調用了Volatile.Read(), 具有Load Memory Barrier(讀屏障),會刷新一次store bufferes,保證獲取到最新的值。
             Read方法會保證函數中,所有在Read方法執行之后的數據讀寫操作,一定實在Read方法執行后才進行
        該方法作用是,當線程在共享區(臨界區)傳遞信息時,通過此方法來原子性的讀取第一個值。
VolatileWrite:原子鎖,立即讀取內存中字段的值,不從寄存器和緩存中讀取,內部調用了Volatile.Write(),  寫入后會刷新一次store bufferes,將新的值即刻寫入內存中。

        所有在Write方法之前執行的數據讀寫操作都在Write方法寫入之前就執行了。該方法作用是,當線程在共享區(臨界區)傳遞信息時,通過此方法來原子性的寫入最后一個值。

這個2個Thread靜態方法用法和System.Threading.Volatile,它提供了兩個靜態方法Write和Read。一樣

這些函數的用法:

哪個線程設定的Volatile變量,就哪個線程使用(誰設定誰使用),其他線程無權讀取,但是可以從新設定該變量名稱。

private void VolatileRW()
        {
            m_stopWork = 0;

            Thread t = new Thread(new ParameterizedThreadStart(Worker));
            t.Start(0);
            Thread.Sleep(5000);
            Thread.VolatileWrite(ref m_stopWork, 1);//設定m_stopWrok為1,這里和順序有關,這里應該用VolatileWrite,不要妄圖去猜想編譯器的優化順序
            LogHelper.WriteInfo("Main thread waiting for worker to stop");
            t.Join();

        }
        private void Worker(object o)
        {
            int x = 0;
            //while (m_stopWork == 0) //如果這樣判定,m_stopWork被緩存后可能不會再去讀取內存的值(循序變量可能會被編譯器優化),所以可能會是個死循環
            while (Thread.VolatileRead(ref m_stopWork) == 0)//用VolatileRead每次就會去讀新的值
            {
                x++;
            }
            LogHelper.WriteInfo(string.Format("worker stoped:x={0}", x));
        }

 

 Interrupt()方法:

其他線程中對目標線程調用interrupt()方法,目標線程將自身的中斷狀態位為true ,線程會不時地檢測這個中斷標示位,以判斷線程是否應該被中斷。並且在拋出異常后立即將線程的中斷標示位清除,即重新設置為false。拋出異常是為了線程從阻塞狀態醒過來,並在結束線程前讓程序員有足夠的時間來處理中斷請求。如果該異常被捕獲,線程就不會終止。如果線程未阻塞就可能在不中斷的情況下運行完成線程。

 

Thread thread = new Thread(() =>
{
    for (int i = 0; i < 10; i++)
    {
        object o = new();
        try
        {
           
                Thread.Sleep(500);//如果該線程內沒有阻塞語句例如 Thread.Sleep(500);那么 thread.Interrupt();將不影響線程執行
            Console.WriteLine(Thread.CurrentThread.ThreadState);
 


        }      ///如果捕獲 Thread.Sleep(1000); 那么其他線程運行thread.Interrupt();將起不到終止線程的效果。所以不要什么異常都捕獲
               ///將會設置該線程的中斷狀態位為true ,線程會不時地檢測這個中斷標示位,以判斷線程是否應該被中斷。並且在拋出異常后立即將線程的中斷標示位清除,
///即重新設置為false。拋出異常是為了線程從阻塞狀態醒過來,並在結束線程前讓程序員有足夠的時間來處理中斷請求。
catch (ThreadInterruptedException ex) { Console.WriteLine($"第{i}次中斷{Thread.CurrentThread.ThreadState}"); } } }); Console.WriteLine(thread.ThreadState); thread.Start(); Console.WriteLine(thread.ThreadState); thread.Interrupt(); Task.Run(() => { Thread.Sleep(1000); thread.Interrupt(); }); thread.Join(); Console.ReadKey();

 

 

 

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

GetDomain    獲取Domain
GetDomainID    獲取DomainID
GetCurrentProcessorId    獲取當前進程ID

 ==========================線程狀態控制==================================================

Start:開始Start()、Start(object)object是方法參數
Join:阻塞直到某個線程終止時為止。
ResetAbort:取消為當前線程請求的 Abort。
Sleep:"將當前線程阻塞指定的毫秒數,Sleep()使得線程立即停止執行,線程將不再得到CPU時間。Thread.Sleep(0)是比較特殊的 表示讓讓出cpu剩余的時間,給線程優先級更高的線程用,如果沒有更高優先級的線程,它自己繼續使用。
當一個任務或者線程調用Thread.Sleep方法時,底層線程會讓出當前處理器時間片的剩余部分,這是一個大開銷的操作。因此,在大部分情況下, 不要在循環內調用Thread.Sleep方法等待特定的條件滿足 。循環內用SpinWait"

============================其他方法==========================================
    
DisableComObjectEagerCleanup    
Finalize:確保垃圾回收器回收 Thread 對象時釋放資源並執行其他清理操作。
GetHashCode    

====================================過時的方法=============================
Suspend:過時 因為容易造成死鎖 掛起 ,調用Suspend()方法 停止線程運行,不是及時的,它要求公共語言運行時必須到達一個安全點,線程將不再得到CPU時間。
  但是可以調用Suspend()方法使得另外一個線程暫停執行。對已經掛起的線程調用Thread.Resume()方法會使其繼續執行。不管使用多少次Suspend()方法來阻塞一個線程,只需一次調用Resume()方法就可以使得線程繼續執行。
  盡可能的不要用Suspend()方法來掛起阻塞線程,因為這樣很容易造成死鎖。假設你掛起了一個線程,而這個線程的資源是其他線程所需要的,會發生什么后果。
  因此,我們盡可能的給重要性不同的線程以不同的優先級,用Thread.Priority()方法來代替使用Thread.Suspend()方法。"
  Resume:過時 恢復掛起
Abort:過時。.NET 5(包括 .NET Core)及更高版本不支持 Thread.Abort 方法,ancellationToken 已成為一個安全且被廣泛接受的 Thread.Abort 替代者。如果線程已經在終止 。Thread.Abort()方法使得系統悄悄的銷毀了線程而且不通知用戶。一旦實施Thread.Abort()操作,該線程不能被重新啟動。調用了這個方法並不是意味着線程立即銷毀,因此為了確定線程是否被銷毀,我們可以調用Thread.Join()來確定其銷毀。對於A和B兩個線程,A線程可以正確的使用Thread.Abort()方法作用於B線程,但是B線程卻不能調用Thread.ResetAbort()來取消Thread.Abort()操作。

GetCompressedStack:過時
SetCompressedStack  :過時
   

操作線程用的枚舉 

ThreadState枚舉

Running :0 線程已啟動且尚未停止。
StopRequested :1 正在請求線程停止。 這僅用於內部。
SuspendRequested :2 正在請求線程掛起。
Background :4 線程正作為后台線程執行(相對於前台線程而言)。 此狀態可以通過設置 IsBackground 屬性來控制。
Unstarted :8 尚未對線程調用 Start() 方法。
Stopped :16 線程已停止。
WaitSleepJoin :32     線程已被阻止。 這可能是調用 Sleep(Int32) 或 Join()、請求鎖定(例如通過調用 Enter(Object) 或 Wait(Object, Int32, Boolean))或在線程同步對象上(例如ManualResetEvent)等待的結果。
Suspended :64 線程已掛起。
AbortRequested :128 已對線程調用了 Abort(Object) 方法,但線程尚未收到試圖終止它的掛起的 ThreadAbortException。
Aborted :256 線程狀態包括 AbortRequested 並且該線程現在已死,但其狀態尚未更改為 Stopped。

ThreadPriority 枚舉

ThreadPriority 枚舉
Lowest :0  可以將 Thread 安排在具有任何其他優先級的線程之后。
Normal :2 可以將 Thread 安排在具有 AboveNormal 優先級的線程之后,在具有 BelowNormal 優先級的線程之前。 默認情況下,線程具有 Normal 優先級。
BelowNormal :1 可以將 Thread 安排在具有 Normal 優先級的線程之后,在具有 Lowest 優先級的線程之前。
AboveNormal :3 可以將 Thread 安排在具有 Highest 優先級的線程之后,在具有 Normal 優先級的線程之前。
Highest:4     可以將 Thread 安排在具有任何其他優先級的線程之前。

ApartmentState枚舉

MTA :1 Thread 將創建並進入一個多線程單元。
STA:0 Thread 將創建並進入一個單線程單元。
Unknown :2 尚未設置 ApartmentState 屬性。

C#中判斷線程的狀態

Thread使用ThreadState屬性指示線程狀態,它 是帶Flags特性的枚舉類型對象。

1.判斷線程是否處於取消狀態

 A.錯誤的判斷

(MyThread.ThreadState == ThreadState.AbortRequested)

B.正確的判斷

(MyThread.ThreadState & ThreadState.AbortRequested) != 0

2.判斷線程是否處於運行狀態    

ThreadState.Running本身等於0,不能用&運算,所以判斷可用以下方法:

(MyThread.ThreadState == ThreadState.Running) 

 

什么是臨界資源和臨界區

1.臨界資源
  臨界資源是一次僅允許一個進程使用的共享資源。各進程采取互斥的方式,實現共享的資源稱作臨界資源。屬於臨界資源的硬件有,打印機,磁帶機等;軟件有消息隊列,變量,數組,緩沖區等。諸進程間采取互斥方式,實現對這種資源的共享。


2.臨界區:
  每個進程中訪問臨界資源的那段代碼稱為臨界區(criticalsection),每次只允許一個進程進入臨界區,進入后,不允許其他進程進入。不論是硬件臨界資源還是軟件臨界資源,多個進程必須互斥的對它進行訪問。多個進程涉及到同一個臨界資源的的臨界區稱為相關臨界區。使用臨界區時,一般不允許其運行時間過長,只要運行在臨界區的線程還沒有離開,其他所有進入此臨界區的線程都會被掛起而進入等待狀態,並在一定程度上影響程序的運行性能。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM