LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

.NET Core 鎖(Lock)底層原理淺談

freeflydom
2024年12月6日 9:18 本文熱度 1406

CPU原子操作

原子操作,指一段邏輯要么全部成功,要么全部失敗。概念上類似數(shù)據(jù)庫(kù)事務(wù)(Transaction).
CPU能夠保證單條匯編的原子性,但不保證多條匯編的原子性
那么在這種情況下,那么CPU如何保證原子性呢?CPU本身也有鎖機(jī)制,從而實(shí)現(xiàn)原子操作

眼見為實(shí)

            int location = 10;
            location++;
            Interlocked.Increment(ref location);


常規(guī)代碼不保證原子性

使用Interlocked類,底層使用CPU鎖來保證原子性

CPU lock前綴保證了操作同一內(nèi)存地址的指令不能在多個(gè)邏輯核心上同時(shí)執(zhí)行

8byte數(shù)據(jù)在32位架構(gòu)的尷尬

思考一個(gè)問題,x86架構(gòu)(注意不是x86-64)的位寬是32位,寄存器一次性最多只能讀取4byte數(shù)據(jù),那么面對(duì)long類型的數(shù)據(jù),它能否保證原子性呢?

可以看到,x86架構(gòu)面對(duì)8byte數(shù)據(jù),分為了兩步走,首先將低8位FFFFFFFF賦值給exa寄存器,再將高8位的7FFFFFFF賦值給edx寄存器。最后再拼接起來。而面對(duì)4byte的int,則一步到位。
前面說到,多條匯編CPU不保證原子性,因此當(dāng)x86架構(gòu)面對(duì)超過4byte的數(shù)據(jù)不保證原子性。

如何解決?

要解決此類尷尬情況,要么使用64位架構(gòu),要么利用CPU的鎖機(jī)制來保證原子性。
C#中的Interlocked.Read為了解決long類型的尷尬而生,是一個(gè)利用CPU鎖很好的例子

用戶態(tài)鎖

操作系統(tǒng)中鎖分為兩種,用戶態(tài)鎖(user-mode)和內(nèi)核態(tài)鎖(kernel-mode)。

  1. 優(yōu)點(diǎn):
    通常性能較高,因?yàn)椴恍枰M(jìn)行用戶態(tài)與內(nèi)核態(tài)的切換,避免了切換帶來的額外開銷,如上下文保存與恢復(fù)等。例如在無競(jìng)爭(zhēng)的情況下,用戶態(tài)的自旋鎖和互斥鎖都可以快速地獲取和釋放鎖,執(zhí)行時(shí)間相對(duì)較短.

  2. 缺點(diǎn):
    在高并發(fā)競(jìng)爭(zhēng)激烈的情況下,如果線程長(zhǎng)時(shí)間獲取不到鎖,自旋鎖會(huì)導(dǎo)致 CPU 空轉(zhuǎn)浪費(fèi)資源,而互斥鎖的等待隊(duì)列管理等也會(huì)在用戶態(tài)消耗一定的 CPU 時(shí)間.

Volatile

在 C# 中,volatile是一個(gè)關(guān)鍵字,用于修飾字段。它告訴編譯器和運(yùn)行時(shí)環(huán)境,被修飾的字段可能會(huì)被多個(gè)線程同時(shí)訪問,并且這些訪問可能是異步的。這意味著編譯器不能對(duì)該字段進(jìn)行某些優(yōu)化,以確保在多線程環(huán)境下能夠正確地讀取和寫入這個(gè)字段的值

//例子1
    static class StrangeBehavior
    {
        private static bool s_stopWorker = false;
        public static void Run()
        {
            Thread t = new Thread(Worker);
            t.Start();
            Thread.Sleep(5000);
            s_stopWorker = true;//5秒之后,work方法應(yīng)該結(jié)束循環(huán)
        }
        private static void Worker()
        {
            int x = 0;
            while (!s_stopWorker)
            {
                x++;
            }
            Console.WriteLine($"worker:stopped when x={x}");//在release模式下,該代碼不執(zhí)行。陷入了死循環(huán)出不來
        }
    }

JIT編譯優(yōu)化的時(shí)候,發(fā)現(xiàn)while (!s_stopWorker)中的s_stopWorker在該方法中永遠(yuǎn)不會(huì)變。因此就自作主張直接生成了while(ture)來“優(yōu)化”代碼

//例子2class MyClass{
    private int _myField;
    public void MyMethod()
    {
        _myField = 5;
        int localVar = _myField;
    }
}

編譯器認(rèn)為_myField被賦值5后,不會(huì)被其它線程改變。所有它會(huì)_myFieId的值直接加載到寄存器中,而后續(xù)使用localVar時(shí),直接從寄存器讀取(CPU緩存),而不是再次從內(nèi)存中讀取。這種優(yōu)化在單線程中是沒有問題的,但在多線程環(huán)境下,會(huì)存在問題。

因此我們需要在變量前,加volatile關(guān)鍵字。來告訴編譯器不要優(yōu)化。

自旋鎖

使用Interlocked實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的自旋鎖

    public struct SpinLockSmiple
    {
        private int _useNum = 0;
        public SpinLockSmiple()
        {
        }
        public void Enter()
        {
            while (true)//一個(gè)死循環(huán),如果鎖競(jìng)爭(zhēng)激烈就會(huì)占用CPU時(shí)間片
            {
                if (Interlocked.Exchange(ref _useNum, 1) == 0)
                    return;
            }
        }
        public void Exit()
        {
			Interlocked.Exchange(ref _useNum, 0);
        }
    }

使用Thread.SpinWait優(yōu)化

上面的自旋鎖有一個(gè)很大問題,就是CPU會(huì)全力運(yùn)算。使用CPU最大的性能。
實(shí)際上,當(dāng)我們沒有獲取到鎖的時(shí)候,完全可以讓CPU“偷懶”一下

    public struct SpinLockSmiple
    {
        private int _useNum = 0;
        public SpinLockSmiple()
        {
        }
        public void Enter()
        {
            while (true)
            {
                if (Interlocked.Exchange(ref _useNum, 1) == 0)
                    return;
				Thread.SpinWait(10);;//讓CPU偷個(gè)懶,不要這么努力的運(yùn)行
            }
        }
        public void Exit()
        {
			Interlocked.Exchange(ref _useNum, 0);
        }
    }

SpinWait函數(shù)在x86平臺(tái)上會(huì)調(diào)用pause指令,pause指令實(shí)現(xiàn)一個(gè)很短的延遲空等待操作,這個(gè)指令的執(zhí)行時(shí)間大概是10+個(gè) CPU時(shí)鐘周期。讓CPU跑得慢一點(diǎn)。

使用SpinWait優(yōu)化

Thread.SpinWait本質(zhì)上是讓CPU偷懶跑得慢一點(diǎn),最多降低點(diǎn)功耗。并沒有讓出CPU時(shí)間片,所以治標(biāo)不治本
因此可以使用SpinWait來進(jìn)一步優(yōu)化。

可以看到,在合適的情況下。SpinWait會(huì)讓出當(dāng)前時(shí)間片,以此提高執(zhí)行效率。比Thread.SpinWait占著資源啥也不做強(qiáng)不少

使用SpinLock代替

SpinLock是C#提供的一種自旋鎖,封裝了管理鎖狀態(tài)和SpinWait.SpinOnce方法的邏輯,雖然做的事情相同,但是代碼更健壯也更容易理解

其底層還是使用的SpinWait

內(nèi)核態(tài)鎖

  1. 優(yōu)點(diǎn):
    內(nèi)核態(tài)鎖由操作系統(tǒng)內(nèi)核管理和調(diào)度,當(dāng)鎖被釋放時(shí),內(nèi)核可以及時(shí)地喚醒等待的線程,適用于復(fù)雜的同步場(chǎng)景和長(zhǎng)時(shí)間等待的情況.

  2. 缺點(diǎn):
    由于涉及到用戶態(tài)與內(nèi)核態(tài)的切換,開銷較大,這在鎖的競(jìng)爭(zhēng)不激烈或者臨界區(qū)執(zhí)行時(shí)間較短時(shí),會(huì)對(duì)性能產(chǎn)生較大的影響

事件(ManualResetEvent/AutoResetEvent)與信號(hào)量(Semaphores)是Windows內(nèi)核中兩種基元線程同步鎖,其它內(nèi)核鎖都是在它們基礎(chǔ)上的封裝

Event鎖

Event鎖有兩種,分為ManualResetEvent\AutoResetEvent 。本質(zhì)上是由內(nèi)核維護(hù)的Int64變量當(dāng)作bool來使,標(biāo)識(shí)0/1兩種狀態(tài),再根據(jù)狀態(tài)決定線程等待與否。

需要注意的是,等待不是原地自旋,并不會(huì)浪費(fèi)CPU性能。而是會(huì)放入CPU _KPRCB結(jié)構(gòu)的WaitListHead鏈表中,不執(zhí)行任何操作。等待系統(tǒng)喚醒

線程進(jìn)入等待狀態(tài)與喚醒可能會(huì)花費(fèi)毫秒級(jí),與自旋的納秒相比,時(shí)間非常長(zhǎng)。所以適合鎖競(jìng)爭(zhēng)非常激烈的場(chǎng)景

眼見為實(shí):是否調(diào)用了win32 API(進(jìn)入內(nèi)核態(tài))?

在Windows上Event對(duì)象通過CreateEventEx函數(shù)來創(chuàng)建,狀態(tài)變化使用Win32 API ResetEvent/SetEvent

眼見為實(shí):內(nèi)核態(tài)中是否真的有l(wèi)ong變量來維護(hù)狀態(tài)?

https://github.com/reactos/reactos/blob/master/sdk/include/xdk/ketypes.h

底層使用SignalState來存儲(chǔ)狀態(tài)

Semaphore鎖

Semaphore的本質(zhì)是由內(nèi)核維護(hù)的Int64變量,信號(hào)量為0時(shí),線程等待。信號(hào)量大于0時(shí),解除等待。
它相對(duì)Event鎖來說,比較特殊點(diǎn)是內(nèi)部使用一個(gè)int64來記錄數(shù)量(limit),舉個(gè)例子,Event鎖管理的是一把椅子是否被坐下,表狀態(tài)。而Semaphore管理的是100把椅子中,有多少坐下,有多少?zèng)]坐下,表臨界點(diǎn)。擁有更多的靈活性。

眼見為實(shí):是否調(diào)用了win32 API(進(jìn)入內(nèi)核態(tài))?

在Windows上信號(hào)量對(duì)象通過CreateSemaphoreEx函數(shù)來創(chuàng)建,增加信號(hào)量使用ReleaseSemaphore,減少信號(hào)量使用WaitForMultipleObject

眼見為實(shí):內(nèi)核態(tài)中是否真的有l(wèi)ong變量來維護(hù)狀態(tài)?

參考Event鎖,它們內(nèi)部共享同一個(gè)結(jié)構(gòu)

Mutex鎖

Mutex是Event與Semaphore的封裝,不做過多解讀。

眼見為實(shí):是否調(diào)用了win32 API(進(jìn)入內(nèi)核態(tài))?

在Windows上,互斥鎖通過CreateMutexEx函數(shù)來創(chuàng)建,獲取鎖用WaitForMultipleObjectsEx,釋放鎖用ReleaseMutex


混合鎖

用戶態(tài)鎖有它的好,內(nèi)核鎖有它的好。把兩者合二為一有沒有搞頭呢?

混合鎖是一種結(jié)合了自旋鎖和內(nèi)核鎖的鎖機(jī)制,在不同的情況下使用不同策略,明顯是一種更好的類型。

Lock

Lock是一個(gè)非常經(jīng)典且常用的混合鎖,其內(nèi)部由兩部分構(gòu)成,也分別對(duì)應(yīng)不同場(chǎng)景下的用戶態(tài)與內(nèi)核態(tài)實(shí)現(xiàn)

  1. 自旋鎖(Thinlock):CoreCLR中別名瘦鎖

  2. 內(nèi)核鎖(AwareLock):CoreClr中別名AwareLock,其底層是AutoResetEvent實(shí)現(xiàn)

Lock鎖先使用用戶態(tài)鎖自旋一定次數(shù),如果獲取不到鎖。再轉(zhuǎn)換成內(nèi)核態(tài)鎖。從而降低CPU消耗。

Lock鎖原理

Lock鎖的原理是在對(duì)象的ObjectHeader上存放一個(gè)線程Id,當(dāng)其它鎖要獲取這個(gè)對(duì)象的鎖時(shí),看一下有沒有存放線程Id,如果有值,說明還被其他鎖持有,那么當(dāng)前線程則會(huì)短暫性自旋,如果在自旋期間能夠拿到鎖,那么鎖的性能將會(huì)非常高。如果自旋一定次數(shù)后,沒有拿到鎖,鎖就會(huì)退化為內(nèi)核鎖。

現(xiàn)在你理解了,為什么lock一定要鎖一個(gè)引用類型吧?

眼見為實(shí):在自旋鎖下ObjectHeader存入了線程Id

點(diǎn)擊查看代碼


眼見為實(shí):在自旋失敗后,退化為內(nèi)核鎖

點(diǎn)擊查看代碼



首先自旋,然后自旋失敗,轉(zhuǎn)成內(nèi)核鎖,并用SyncBlock 來維護(hù)鎖相關(guān)的統(tǒng)計(jì)信息,01代表SyncBlock的Index,08是一個(gè)常量,代表內(nèi)核鎖

其它混合鎖

基本上以Slim結(jié)尾的鎖,都是混合鎖。都是先自旋一定次數(shù),再進(jìn)入內(nèi)核態(tài)。
比如ReaderWriterSlim,SemaphoreSlim,ManualResetEventSlim.

異步鎖

在C#中,SemaphoreSlim可以在一定程度上用于異步場(chǎng)景。它可以限制同時(shí)訪問某個(gè)資源的異步操作的數(shù)量。例如,在一個(gè)異步的 Web 請(qǐng)求處理場(chǎng)景中,可以使用SemaphoreSlim來控制同時(shí)處理請(qǐng)求的數(shù)量。然而,它并不能完全替代真正的異步鎖,因?yàn)樗饕强刂撇l(fā)訪問的數(shù)量,而不是像傳統(tǒng)鎖那樣提供互斥訪問

Nito.AsyncEx 介紹

https://github.com/StephenCleary/AsyncEx
大神維護(hù)了的一個(gè)異步鎖的開源庫(kù),它將同步版的鎖結(jié)構(gòu)都做了一份異步版,彌補(bǔ)了.NET框架中的對(duì)異步鎖支持不足的遺憾

無鎖算法

即使是最快的鎖,也數(shù)倍慢于沒有鎖的代碼,因從CAS無鎖算法應(yīng)運(yùn)而生。
無鎖算法大量依賴原子操作,如比較并交換(CAS,Compare - And - Swap)、加載鏈接 / 存儲(chǔ)條件(LL/SC,Load - Linked/Store - Conditional)等。以 CAS 為例,它是一種原子操作,用于比較一個(gè)內(nèi)存位置的值與預(yù)期值,如果相同,就將該位置的值更新為新的值。
舉個(gè)例子

internal class Program{
    public static DualCounter Counter = new DualCounter(0, 0);
    static void Main(string[] args)
    {
        Task.Run(IncrementCounters);
        Task.Run(IncrementCounters);
        Task.Run(IncrementCounters);
        Console.ReadLine();
    }
    public static DualCounter Increment(ref DualCounter counter)
    {
        DualCounter oldValue, newValue;
        do
        {
            oldValue = counter;//1. 線程首先讀取counter的當(dāng)前值,存為oldvalue
            newValue = new DualCounter(oldValue.A + 1, oldValue.B + 1);//2. 計(jì)算出新的值,作為預(yù)期值
        }
        while (Interlocked.CompareExchange(ref counter, newValue, oldValue) != oldValue);//3. 利用原子操作比較兩者的值,如果操作失敗,說明counter的值已經(jīng)被其它線程修改,需要重新讀取,直到成功。
        return newValue;
    }
    public static void IncrementCounters()
    {
        var result = Increment(ref Counter);
        Console.WriteLine("{0},{1}",result.A,result.B);
    }
}public class DualCounter{
    public int A { get; }
    public int B { get; }
    public DualCounter(int a,int b)
    {
        A = a;
        B = b;
    }
}

無鎖算法的優(yōu)缺點(diǎn)

上面提到的無鎖算法不一定比使用線程快。比如

  1. 每次都要New對(duì)象分配內(nèi)存,這個(gè)取決于你的業(yè)務(wù)復(fù)雜度。

  2. 如果Interlocked.CompareExchange一直交換失敗,會(huì)類似自旋鎖一樣大量占用CPU資源

簡(jiǎn)單匯總一下

  1. 優(yōu)點(diǎn):

  • 高性能:由于避免了鎖的開銷,如線程的阻塞和喚醒、上下文切換等,無鎖算法在高并發(fā)場(chǎng)景下可能具有更好的性能。特別是當(dāng)鎖競(jìng)爭(zhēng)激烈時(shí),無鎖算法能夠更有效地利用系統(tǒng)資源,減少線程等待時(shí)間。

  • 可擴(kuò)展性好:無鎖算法在多核處理器環(huán)境下能夠更好地發(fā)揮多核的優(yōu)勢(shì),因?yàn)槎鄠€(gè)線程可以同時(shí)對(duì)共享數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作,而不受傳統(tǒng)鎖機(jī)制的限制,能夠更好地支持大規(guī)模的并發(fā)訪問。

  1. 缺點(diǎn):

  • 實(shí)現(xiàn)復(fù)雜:無鎖算法的設(shè)計(jì)和實(shí)現(xiàn)相對(duì)復(fù)雜,需要深入理解底層的原子操作、內(nèi)存模型和并發(fā)編程原理。錯(cuò)誤的實(shí)現(xiàn)可能會(huì)導(dǎo)致數(shù)據(jù)不一致、死鎖或者活鎖等問題。

  • ABA 問題:這是無鎖算法中常見的一個(gè)問題。例如在使用 CAS 操作時(shí),一個(gè)內(nèi)存位置的值從 A 變?yōu)?B,然后又變回 A,這可能會(huì)導(dǎo)致一些無鎖算法誤判。解決 ABA 問題通常需要額外的標(biāo)記或者版本號(hào)機(jī)制來記錄內(nèi)存位置的變化歷史。

  • 內(nèi)存順序問題:在多核處理器環(huán)境下,由于處理器緩存和指令重排等因素,無鎖算法需要考慮內(nèi)存順序問題,以確保不同線程對(duì)共享數(shù)據(jù)結(jié)構(gòu)的操作順序符合預(yù)期,避免出現(xiàn)數(shù)據(jù)不一致的情況。這通常需要使用內(nèi)存屏障等技術(shù)來輔助解決。

?轉(zhuǎn)自https://www.cnblogs.com/lmy5215006/p/18585588


該文章在 2024/12/6 9:24:21 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
亚洲日本欧美视频网站 | 网友久久更新新视频免费 | 欧美黑人激情性久久 | 日韩欧美一区二区在线观看 | 中文乱码在线波多野结衣 | 欧美国产一级毛卡片免费 |