常見的“鎖”有哪些?

悲觀鎖

悲觀鎖認為在并發環境中,數據隨時可能被其他線程修改,因此在訪問數據之前會先加鎖,以防止其他線程對數據進行修改。常見的悲觀鎖實現有:

1.互斥鎖

原理:互斥鎖是一種最基本的鎖類型,同一時間只允許一個線程訪問共享資源。當一個線程獲取到互斥鎖后,其他線程如果想要訪問該資源,就必須等待鎖被釋放。

應用場景:適用于寫操作頻繁的場景,如數據庫中的數據更新操作。在 C++ 中可以使用?std::mutex?來實現互斥鎖,示例代碼如下:

#include <iostream>
#include <mutex>
#include <thread>std::mutex mtx;
int sharedResource = 0;void increment() {std::lock_guard<std::mutex> lock(mtx);sharedResource++;
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Shared resource: " << sharedResource << std::endl;return 0;
}

2.讀寫鎖

原理:讀寫鎖允許多個線程同時進行讀操作,但在進行寫操作時,會獨占資源,不允許其他線程進行讀或寫操作。讀寫鎖分為讀鎖和寫鎖,多個線程可以同時獲取讀鎖,但寫鎖是排他的。

應用場景:適用于讀多寫少的場景,如緩存系統。在 C++ 中可以使用?std::shared_mutex?來實現讀寫鎖,示例代碼如下:?

#include <iostream>
#include <shared_mutex>
#include <thread>std::shared_mutex rwMutex;
int sharedData = 0;void readData() {std::shared_lock<std::shared_mutex> lock(rwMutex);std::cout << "Read data: " << sharedData << std::endl;
}void writeData() {std::unique_lock<std::shared_mutex> lock(rwMutex);sharedData++;std::cout << "Write data: " << sharedData << std::endl;
}int main() {std::thread t1(readData);std::thread t2(writeData);t1.join();t2.join();return 0;
}

    樂觀鎖

    樂觀鎖是一種在多線程環境中避免阻塞的同步技術,它假設大部分操作是不會發生沖突的,因此在操作數據時不會直接加鎖,而是通過檢查數據是否發生了變化來決定是否提交。如果在提交數據時發現數據已被其他線程修改,則會放棄當前操作,重新讀取數據并重試。

    應用場景:適用于讀多寫少、沖突較少的場景,如電商系統中的庫存管理。

    在 C++ 中,樂觀鎖的實現通常依賴于版本號時間戳的機制。每個線程在操作數據時,會記錄數據的版本或時間戳,操作完成后再通過比較版本號或時間戳來判斷是否發生了沖突。

    下面是一個使用版本號實現樂觀鎖的簡單示例代碼:

    #include <iostream>
    #include <thread>
    #include <atomic>
    #include <chrono>// 共享數據結構
    struct SharedData {int value;            // 數據的實際值std::atomic<int> version; // 數據的版本號,用于檢查是否發生了修改
    };// 線程安全的樂觀鎖實現
    bool optimisticLockUpdate(SharedData& data, int expectedVersion, int newValue) {// 檢查數據的版本號是否與預期一致if (data.version.load() == expectedVersion) {// 進行數據更新data.value = newValue;// 增加版本號data.version.fetch_add(1, std::memory_order_relaxed);return true; // 成功提交更新}return false; // 數據版本不一致,操作失敗
    }void threadFunction(SharedData& data, int threadId) {int expectedVersion = data.version.load();int newValue = threadId * 10;std::cout << "Thread " << threadId << " starting with version " << expectedVersion << "...\n";std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模擬工作// 嘗試更新數據if (optimisticLockUpdate(data, expectedVersion, newValue)) {std::cout << "Thread " << threadId << " successfully updated value to " << newValue << "\n";} else {std::cout << "Thread " << threadId << " failed to update (version mismatch)\n";}
    }int main() {// 初始化共享數據,值為 0,版本號為 0SharedData data{0, 0};// 啟動多個線程進行樂觀鎖測試std::thread t1(threadFunction, std::ref(data), 1);//std::ref(data) 將 data 包裝成一個引用包裝器,確保 data 在傳遞給函數時以引用的方式傳遞,而不是被復制。std::thread t2(threadFunction, std::ref(data), 2);std::thread t3(threadFunction, std::ref(data), 3);t1.join();t2.join();t3.join();std::cout << "Final value: " << data.value << ", Final version: " << data.version.load() << "\n";return 0;
    }

    原子鎖

    原子鎖是一種基于原子操作(如CAS、test_and_set)的鎖機制。與傳統的基于互斥量(如 std::mutex)的鎖不同,原子鎖依賴于硬件提供的原子操作,允許對共享資源的訪問進行同步,且通常比傳統鎖更加高效。它通過原子操作保證對共享資源的獨占訪問,而不需要顯式的線程調度。

    原子鎖的適用場景:

    1.簡單數據類型:原子鎖最常用于鎖定簡單的基礎數據類型,例如整數、布爾值、指針等。通過原子操作,多個線程可以安全地對這些數據進行讀寫,而不會發生數據競爭。

    示例:std::atomic<int>,?std::atomic<bool>,?std::atomic<long long>

    2.計數器、標志位:當需要在多線程中維護計數器、標志位或狀態變量時,原子操作非常合適。例如,當多個線程需要遞增計數器時,可以用原子操作避免使用傳統的互斥鎖。

    示例:使用?std::atomic<int>?來維護線程安全的計數器。

    注:原子鎖通常不能鎖容器類型。

    什么是原子操作?

    原子操作是指不可分割的操作,在執行過程中不會被中斷或干擾。原子操作保證了操作的完整性,要么完全執行,要么完全不執行,避免了在操作過程中被線程切換打斷,從而避免了數據競爭和不一致的情況。

    1.自旋鎖

    什么是自旋鎖?

    自旋鎖是一種使用原子操作來檢測鎖是否可用的鎖機制。自旋鎖是一種忙等待的鎖,當線程嘗試獲取鎖失敗時,會不斷地檢查鎖的狀態,直到成功獲取鎖。?

    在 C++ 中,可以使用?std::atomic_flag?結合?test_and_set?操作來實現一個簡單的自旋鎖:

    test_and_set?是一個原子操作,它會檢查一個布爾標志的值,然后將該標志設置為?true。整個操作過程是不可分割的,即不會被其他線程的操作打斷。這個布爾標志通常被用作鎖,線程通過檢查并設置這個標志來嘗試獲取鎖。

    工作原理

    • 檢查標志狀態:線程首先檢查布爾標志的當前值。
    • 設置標志為 true:如果標志當前為 false,表示鎖未被占用,線程將標志設置為 true,表示成功獲取到鎖;如果標志當前為 true,表示鎖已被其他線程占用,線程未能獲取到鎖。
    • 返回舊值:test_and_set 操作會返回標志的舊值。線程可以根據這個返回值判斷是否成功獲取到鎖。如果返回 false,說明成功獲取到鎖;如果返回 true,則需要等待鎖被釋放后再次嘗試獲取。
    #include <iostream>
    #include <atomic>
    #include <thread>
    #include <vector>std::atomic_flag lock = ATOMIC_FLAG_INIT;// 自旋鎖類
    class SpinLock {
    public:void lock() {// 持續嘗試獲取鎖,直到成功while (lock.test_and_set(std::memory_order_acquire)) {// 自旋等待}}void unlock() {// 釋放鎖,將標志設置為 falselock.clear(std::memory_order_release);}
    };SpinLock spinLock;
    int sharedResource = 0;// 線程函數
    void worker() {for (int i = 0; i < 100000; ++i) {spinLock.lock();++sharedResource;spinLock.unlock();}
    }int main() {std::vector<std::thread> threads;// 創建多個線程for (int i = 0; i < 4; ++i) {threads.emplace_back(worker);}// 等待所有線程完成for (auto& thread : threads) {thread.join();}std::cout << "Shared resource value: " << sharedResource << std::endl;return 0;
    }

    自旋鎖優點:

    1. 無上下文切換:自旋鎖不會引起線程掛起,因此避免了上下文切換的開銷。在鎖競爭較輕時,自旋鎖可以高效地工作。

    2. 簡單高效:實現簡單,且不依賴操作系統調度,適合鎖競爭不嚴重的場景。

    自旋鎖缺點:

    1. CPU資源浪費:如果鎖被占用,自旋鎖會不斷地循環檢查鎖的狀態,浪費 CPU 時間,尤其是在鎖持有時間較長時,可能導致性能問題。

    2. 不適合鎖競爭場景:當有大量線程競爭同一個鎖時,自旋鎖的性能將大幅下降,因為大部分時間都在自旋,浪費了 CPU 資源。

    自旋鎖的適用場景:

    1. 短時間鎖競爭:自旋鎖適用于臨界區代碼執行時間非常短的情況。如果鎖持有時間較長,使用自旋鎖就不合適了。

    2. 鎖競爭較輕:在多線程程序中,如果線程數量較少且資源競爭較少,自旋鎖可以有效減少線程上下文切換,提升性能。

    3. 實時系統或高性能系統:在某些對延遲非常敏感的應用場景中,自旋鎖可以通過減少上下文切換來提供更低的延遲。

    總結:自旋鎖是一種簡單且高效的鎖機制,通過原子操作避免了線程上下文切換,適合用于短時間鎖競爭和低延遲要求的場景。在鎖競爭激烈或鎖持有時間較長時,自旋鎖的性能會受到影響,這時傳統的互斥鎖(如 std::mutex)可能更為合適。

    遞歸鎖

    在 C++ 中,遞歸鎖也被稱為可重入鎖,它是一種特殊的鎖機制,允許同一個線程多次獲取同一把鎖而不會產生死鎖。

    原理

    普通的互斥鎖(如?std::mutex)不允許同一個線程在已經持有鎖的情況下再次獲取該鎖,否則會導致死鎖。因為當線程第一次獲取鎖后,鎖處于被占用狀態,再次嘗試獲取時,由于鎖未被釋放,線程會被阻塞,而該線程又因為被阻塞無法釋放鎖,從而陷入死循環。

    遞歸鎖則不同,它內部維護了一個計數器和一個持有鎖的線程標識。當一個線程第一次獲取遞歸鎖時,計數器加 1,同時記錄該線程的標識。如果該線程再次請求獲取同一把鎖,計數器會繼續加 1,而不會被阻塞。當線程釋放鎖時,計數器減 1,直到計數器為 0 時,鎖才會真正被釋放,其他線程才可以獲取該鎖。

    應用場景:

    • 遞歸調用:在遞歸函數中,如果需要對共享資源進行保護,使用遞歸鎖可以避免死鎖問題。例如,在一個遞歸遍歷樹結構的函數中,可能需要對樹節點的某些屬性進行修改,此時可以使用遞歸鎖來保證線程安全。
    • 嵌套鎖:當代碼中存在多層嵌套的鎖獲取操作,且這些操作可能由同一個線程執行時,遞歸鎖可以避免死鎖。例如,一個函數內部調用了另一個函數,這兩個函數都需要獲取同一把鎖。

    注意事項:

    1. 性能開銷

    遞歸鎖的實現比普通互斥鎖更為復雜。普通互斥鎖只需簡單地標記鎖的占用狀態,當一個線程請求鎖時,檢查該狀態并進行相應操作。而遞歸鎖除了要維護鎖的占用狀態,還需要記錄持有鎖的線程標識以及一個計數器,用于跟蹤同一個線程獲取鎖的次數。每次獲取和釋放鎖時,都需要對這些額外信息進行更新和檢查,這無疑增加了系統的開銷。

    • 時間開銷:由于額外的狀態檢查和更新操作,遞歸鎖的加鎖和解鎖操作通常比普通互斥鎖更耗時。在高并發、對性能要求極高的場景下,頻繁使用遞歸鎖可能會成為性能瓶頸。
    • 資源開銷:記錄線程標識和計數器需要額外的內存空間,雖然這部分開銷相對較小,但在資源受限的系統中,也可能會產生一定的影響。

    建議:在不需要遞歸獲取鎖的場景下,應優先使用普通互斥鎖(如 std::mutex)。

    2. 死鎖風險

    雖然遞歸鎖允許同一個線程多次獲取同一把鎖而不會死鎖,但如果在遞歸調用過程中,鎖的獲取和釋放邏輯出現錯誤,仍然可能導致死鎖。例如,在遞歸函數中,獲取鎖后在某些條件下沒有正確釋放鎖就進行了遞歸調用,可能會導致鎖無法正常釋放,其他線程請求該鎖時就會陷入死鎖。

    #include <iostream>
    #include <thread>
    #include <mutex>std::recursive_mutex recMutex;void faultyRecursiveFunction(int n) {if (n == 0) return;std::lock_guard<std::recursive_mutex> lock(recMutex);std::cout << "Recursive call: " << n << std::endl;if (n == 2) {// 錯誤:沒有釋放鎖就返回,可能導致死鎖return;}faultyRecursiveFunction(n - 1);
    }int main() {std::thread t(faultyRecursiveFunction, 3);t.join();return 0;
    }
    

    3.不同遞歸鎖之間的交叉鎖定

    當存在多個遞歸鎖時,如果不同線程以不同的順序獲取這些鎖,就可能會產生死鎖。例如,線程 A 先獲取了遞歸鎖 L1,然后嘗試獲取遞歸鎖 L2;而線程 B 先獲取了遞歸鎖 L2,然后嘗試獲取遞歸鎖 L1。此時,兩個線程都在等待對方釋放鎖,從而陷入死鎖狀態。

    在 C++ 標準庫中,std::recursive_mutex?是遞歸鎖的實現。以下是一個簡單的示例代碼:

    #include <iostream>
    #include <thread>
    #include <mutex>std::recursive_mutex recMutex;// 遞歸函數,多次獲取遞歸鎖
    void recursiveFunction(int n) {if (n == 0) return;// 加鎖std::lock_guard<std::recursive_mutex> lock(recMutex);std::cout << "Recursive call: " << n << std::endl;// 遞歸調用recursiveFunction(n - 1);// 鎖在離開作用域時自動釋放
    }int main() {std::thread t(recursiveFunction, 3);t.join();return 0;
    }
    

    什么是鎖的重入與不可重入?

    可重入鎖也叫遞歸鎖,允許同一個線程在已經持有該鎖的情況下,再次獲取同一把鎖而不會產生死鎖。可重入鎖內部會維護一個持有鎖的線程標識和一個計數器。當線程第一次獲取鎖時,會記錄該線程的標識,并將計數器初始化為 1。如果該線程再次請求獲取同一把鎖,鎖會檢查請求線程的標識是否與當前持有鎖的線程標識相同,如果相同,則將計數器加 1,而不會阻塞該線程。釋放鎖時,計數器減 1,直到計數器為 0 時,鎖才會釋放,其他線程才可以獲取該鎖。

    不可重入鎖不允許同一個線程在已經持有該鎖的情況下再次獲取同一把鎖。如果一個線程已經持有了不可重入鎖,再次請求獲取該鎖時,會導致線程阻塞,進而可能產生死鎖。不可重入鎖只關注鎖的占用狀態,不記錄持有鎖的線程標識和獲取鎖的次數。當一個線程請求獲取鎖時,鎖會檢查其是否已被占用,如果已被占用,無論請求線程是否就是持有鎖的線程,都會將該線程阻塞。

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/web/70529.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/web/70529.shtml
    英文地址,請注明出處:http://en.pswp.cn/web/70529.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    深入理解 Python 作用域:從基礎到高級應用

    在 Python 編程中&#xff0c;作用域是一個至關重要的概念&#xff0c;它決定了變量和函數的可見性與生命周期。正確理解和運用作用域規則&#xff0c;對于編寫結構清晰、易于維護的代碼起著關鍵作用。無論是簡單的腳本還是復雜的大型項目&#xff0c;作用域都貫穿其中&#xf…

    ubuntu磁盤清理垃圾文件

    大頭文件排查 #先查看是否是內存滿了&#xff0c;USER 很高即是滿了 du -f#抓大頭思想&#xff0c;優先刪除大文件#查看文件目錄 內存占用量并排序&#xff0c;不斷文件遞歸下去 du --max-depth1 -h /home/ -h | sort du --max-depth1 -h /home/big/ -h | sort 緩存文件清理…

    ctf網絡安全題庫 ctf網絡安全大賽答案

    此題解僅為部分題解&#xff0c;包括&#xff1a; 【RE】&#xff1a;①Reverse_Checkin ②SimplePE ③EzGame 【Web】①f12 ②ezrunner 【Crypto】①MD5 ②password ③看我回旋踢 ④摩絲 【Misc】①爆爆爆爆 ②凱撒大帝的三個秘密 ③你才是職業選手 一、 Re ① Reverse Chec…

    VSCode集成deepseek使用介紹(Visual Studio Code)

    VSCode集成deepseek使用介紹&#xff08;Visual Studio Code&#xff09; 1. 簡介 隨著AI輔助編程工具的快速發展&#xff0c;VSCode作為一款輕量級、高度可擴展的代碼編輯器&#xff0c;已成為開發者首選的工具之一。DeepSeek作為AI模型&#xff0c;結合Roo Code插件&#x…

    git 常用功能

    以下是 Git 的常用功能及其命令&#xff1a; 初始化倉庫 git init在當前目錄初始化一個新的 Git 倉庫。 克隆倉庫 git clone <倉庫地址>將遠程倉庫克隆到本地。 查看狀態 git status查看工作區和暫存區的狀態。 添加文件到暫存區 git add <文件名>將文件添…

    Unity 腳本控制3D人物模型的BlendShape

    有些3D角色模型帶有BlendShape面部控制, 在Unity中可以通過接口訪問并操作其參數可以表現不同的面部表情 在Unity中選中角色模型的指定部位,這個是由模型師定義的,不固定.但肯定是在面部建模上. 點選之后在檢查器可以看到對應的BlendShapes設定項出現在SkinedMeshRenderer組件…

    vscode設置終端復制快捷鍵(有坑!!!)

    vscode的編輯頁面和終端的復制粘貼快捷鍵是不一樣的。 vscode的終端復制快捷鍵為ctrlshiftC&#xff0c;當然&#xff0c;自己可以自定義設置 vscode設置終端復制快捷鍵&#xff08;有坑&#xff01;&#xff01;&#xff01;&#xff09;_vs code 不能復制-CSDN博客文章瀏覽…

    Ansible 學習筆記

    這里寫自定義目錄標題 基本架構文件結構安裝查看版本 Ansible 配置相關文件主機清單寫法 基本架構 Ansible 是基于Python實現的&#xff0c;默認使用22端口&#xff0c; 文件結構 安裝 查看用什么語言寫的用一下命令 查看版本 Ansible 配置相關文件 主機清單寫法

    0083.基于springboot+uni-app的社區車位租賃系統小程序+論文

    一、系統說明 基于springbootuni-app的社區車位租賃系統小程序,系統功能齊全, 代碼簡潔易懂&#xff0c;適合小白學編程。 現如今&#xff0c;信息種類變得越來越多&#xff0c;信息的容量也變得越來越大&#xff0c;這就是信息時代的標志。近些年&#xff0c;計算機科學發展…

    NavVis VLX三維掃描:高層建筑數字化的革新力量【滬敖3D】

    在三維激光掃描領域&#xff0c;樓梯結構因其復雜的空間形態和連續垂直移動的實際需求&#xff0c;一直是技術難點之一。利用NavVis VLX穿戴式移動掃描系統成功完成一棟34層建筑的高效掃描&#xff0c;其中樓梯部分的數據一遍成形且無任何分層或形變。本文將深入分析該項目的技…

    3D模型在線轉換工具:輕松實現3DM轉OBJ

    3D模型在線轉換是一款功能強大的在線工具&#xff0c;支持多種3D模型格式的在線預覽和互轉。無論是工業設計、建筑設計&#xff0c;還是數字藝術領域&#xff0c;這款工具都能滿足您的需求。 3DM與OBJ格式簡介 3DM格式&#xff1a;3DM是一種廣泛應用于三維建模的文件格式&…

    引入elementUI時報錯undefined is not an object (evaluating ‘h.a.prototype‘)

    把這兩個引入方式都做了 于是報錯&#xff1a; 把CDN的刪掉就好了。

    PHP商協會管理系統小程序源碼

    &#x1f4ca; 商協會管理系統 &#x1f4bb; 這是一款基于ThinkPHPUniapp框架&#xff0c;經過深度定制與匠心打造的商協會系統&#xff0c;被譽為商協會領域數字化運營管理的新銳之星。它以“智慧化會員體系、智敏化內容運營、智能化活動構建”為三大核心動力源&#xff0c;…

    端邊云架構

    端邊云架構是一種分布式計算架構&#xff0c;它將計算任務分布在終端設備、邊緣節點和云端服務器之間&#xff0c;以實現高效的數據處理和資源管理。這種架構在現代物聯網&#xff08;IoT&#xff09;、智能城市、工業互聯網等場景中得到了廣泛應用。以下是端邊云架構的主要組成…

    用AI寫游戲3——deepseek實現kotlin android studio greedy snake game 貪吃蛇游戲

    項目下載 https://download.csdn.net/download/AnalogElectronic/90421306 項目結構 就是通過android studio 建空項目&#xff0c;改下MainActivity.kt的內容就完事了 ctrlshiftalts 看項目結構如下 核心代碼 MainActivity.kt package com.example.snakegame1// MainA…

    【數據庫系統概論】數據庫設計

    7.1 數據庫設計概述 定義 數據庫設計是指對于一個給定的應用環境&#xff0c;構造&#xff08;設計&#xff09; 優化的 數據庫模式、內模式和外模式&#xff0c;并據此建立數據庫及其 應用系統 &#xff0c;使之能夠有效地存儲和管理數據&#xff0c;滿足各種用戶的應用需求…

    Element UI日期選擇器默認顯示1970年解決方案

    目錄 問題背景 問題根源 1. 數據綁定類型錯誤 2. 初始化邏輯錯誤 解決方案 核心思路 步驟 1&#xff1a;正確初始化日期對象 步驟 2&#xff1a;處理數據交互 步驟 3&#xff1a;處理年份切換事件 完整代碼示例 注意事項 1. 時區問題 2. 格式化綁定值 常見問題 1. 為什…

    kafka-保姆級配置說明(producer)

    配置說明的最后一部分&#xff1b; ##指定kafka集群的列表&#xff0c;以“,”分割&#xff0c;格式&#xff1a;“host:port,host:port” ##此列表用于producer&#xff08;consumer&#xff09;初始化連接使用&#xff0c;server列表可以為kafka集群的子集 ##通過此servers列…

    .NET周刊【2月第2期 2025-02-09】

    國內文章 開箱即用的.NET MAUI組件庫 V-Control 發布了! https://www.cnblogs.com/jevonsflash/p/18701494 文章介紹了V-Control&#xff0c;一個適用于.NET MAUI的組件庫。作者計劃將其開源&#xff0c;強調.NET MAUI是生產力強的跨平臺移動開發工具。V-Control提供多種組件…

    PHP2(WEB)

    ##解題思路 打開頁面什么線索都沒有&#xff0c;目錄掃描只是掃出來一個index.php&#xff0c;而源代碼沒有東西&#xff0c;且/robots.txt是不允許訪問的 于是一番查詢后發現&#xff0c;有個index.phps的文件路徑&#xff0c;里頭寫著一段php的邏輯&#xff0c;對url的id參數…