目錄
1.基本互斥鎖(std::mutex)
2.遞歸互斥鎖(std::recursive_mutex)
3.帶超時機制的互斥鎖(std::timed_mutex)
4.帶超時機制的遞歸互斥鎖(std::recursive_timed_mutex)
7.自旋鎖
8.總結
1.基本互斥鎖(std::mutex)
含義: std::mutex
是最基本的互斥鎖,主要用于保護臨界區,確保同一時間只有一個線程可以訪問共享資源。
使用場景: 當需要保護共享資源不被多個線程同時修改時使用。
特點:簡單易用,適用于大多數場景;不能遞歸鎖定,同一線程多次嘗試鎖定會導致死鎖。
以下是一個簡單的示例,展示了如何使用?std::mutex
?來保護共享數據:
#include <iostream>
#include <thread>
#include <mutex> std::mutex mtx; //全局互斥鎖
int shared_data = 0; //共享數據void increment_shared_data(int n) { for (int i = 0; i < n; ++i) { std::lock_guard<std::mutex> lock(mtx); ++shared_data; }
} int main() { std::thread t1(increment_shared_data, 1000); std::thread t2(increment_shared_data, 1000); t1.join(); t2.join(); std::cout << "Shared data: " << shared_data << std::endl; return 0;
}
?這個程序創建了2個線程,每個線程嘗試對counter
增加10000次。通過使用std::mutex
, 我們確保每次只有一個線程可以增加計數器,避免了數據競爭。
2.遞歸互斥鎖(std::recursive_mutex)
含義:std::recursive_mutex
允許同一線程多次獲取鎖而不會發生死鎖,這對于遞歸函數或需要多次鎖定的場景非常有用。
使用場景: 在遞歸函數中需要多次獲取同一個鎖的情況。
特點:適用于遞歸調用和需要多次鎖定的場景;需要注意避免濫用,因為遞歸鎖的使用會增加鎖定次數的復雜性。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>std::recursive_mutex rmtx;void recursive_function(int depth) {rmtx.lock();std::cout << "Depth: " << depth << std::endl;if (depth > 0) {recursive_function(depth - 1);}rmtx.unlock();
}int main() {std::thread t(recursive_function, 5);t.join();return 0;
}
這段代碼在遞歸函數recursive_function
中使用std::recursive_mutex
。每次調用都會嘗試加鎖,由于使用的是遞歸互斥鎖,同一線程可以多次成功獲取鎖。
3.帶超時機制的互斥鎖(std::timed_mutex)
含義:std::timed_mutex
在std::mutex
的基礎上增加了超時功能,允許線程在指定時間內嘗試獲取鎖,如果在超時時間內未成功獲取鎖,則返回失敗。
使用場景: 當你不希望線程因等待鎖而無限期阻塞時使用。
特點:適用于需要設置鎖獲取超時時間的場景;提供try_lock_for
和try_lock_until
兩種超時嘗試獲取鎖的方法。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono> std::timed_mutex mtx; void try_lock_function() { if (mtx.try_lock_for(std::chrono::seconds(1))) { std::cout << "Lock acquired!\n"; // 執行受保護的操作 std::this_thread::sleep_for(std::chrono::seconds(2)); // 模擬耗時操作 mtx.unlock(); // 顯式解鎖 } else { std::cout << "Failed to acquire lock within timeout.\n"; }
} int main() { std::thread t1(try_lock_function); std::thread t2(try_lock_function); t1.join(); t2.join(); return 0;
}
在這個例子中,兩個線程都嘗試在 1 秒內獲取鎖。由于互斥鎖在同一時刻只能被一個線程持有,因此至少有一個線程將無法在超時時間內獲取鎖,并輸出相應的消息。
4.帶超時機制的遞歸互斥鎖(std::recursive_timed_mutex)
含義:std::recursive_timed_mutex
結合了std::recursive_mutex
和std::timed_mutex
的特性,支持遞歸鎖定和超時機制。
使用場景: 適用于需要遞歸鎖定資源,并且希望能夠設置嘗試獲取鎖的超時時間的場景。這在需要防止線程在等待鎖時無限阻塞的復雜遞歸調用中特別有用。
特點:適用于遞歸調用和需要超時機制的場景;提供超時嘗試獲取遞歸鎖的方法。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono> std::recursive_timed_mutex mtx; void recursive_lock_function() { if (mtx.try_lock_for(std::chrono::seconds(1))) { std::cout << "Lock acquired!\n"; // 遞歸鎖定 if (mtx.try_lock_for(std::chrono::seconds(1))) { std::cout << "Recursive lock acquired!\n"; mtx.unlock(); // 釋放遞歸鎖 } else { std::cout << "Failed to acquire recursive lock within timeout.\n"; } // ... 執行受保護的操作 mtx.unlock(); // 釋放原始鎖 } else { std::cout << "Failed to acquire lock within timeout.\n"; }
} int main() { std::thread t1(recursive_lock_function); std::thread t2(recursive_lock_function); t1.join(); t2.join(); return 0;
}
請注意,由于?std::recursive_timed_mutex
?允許遞歸鎖定,上面的示例中展示了如何在已經持有鎖的情況下再次嘗試獲取鎖(盡管在這個特定示例中,第二次嘗試獲取鎖是多余的,因為我們已經持有鎖了)。然而,在實際情況中,遞歸鎖定可能用于更復雜的場景,其中函數可能會遞歸調用自己,并且每個遞歸調用都需要訪問受保護的數據。
5.共享互斥鎖也叫讀寫鎖(std::shared_mutex)
含義:std::shared_mutex
允許多個線程同時讀取,但只有一個線程可以寫入。這在讀多寫少的場景下非常有用。
使用場景: 適用于讀操作遠多于寫操作的情況。
特點:適用于讀多寫少的場景;讀操作和寫操作使用不同的鎖定機制。
示例如下:
#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_mutex shmtx;void read_shared(int id) {std::shared_lock<std::shared_mutex> lock(shmtx); // 共享鎖std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}void write_shared(int id) {std::unique_lock<std::shared_mutex> lock(shmtx); // 獨占鎖std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));
}int main() {std::thread readers[5], writer(write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}
輸出結果可能會有所不同,因為讀寫順序由操作系統的線程調度決定。本例中,一個寫線程在修改數據,多個讀線程在同時讀數據。通過std::shared_mutex
,我們允許多個讀操作同時進行,但寫操作是獨占的。
6.帶超時機制的共享互斥鎖(std::shared_timed_mutex)
含義:std::shared_timed_mutex
?是 C++ 標準庫中的一個同步原語,它結合了?std::shared_mutex
(共享互斥鎖)和超時機制的特性。std::shared_mutex
?允許多個線程同時以共享模式持有鎖(即讀取操作可以并發執行),但每次只有一個線程能以獨占模式持有鎖(即寫入操作是互斥的)。通過添加超時機制,std::shared_timed_mutex
?允許線程嘗試以共享模式或獨占模式獲取鎖,并設置一個超時時間,如果在這段時間內未能成功獲取鎖,則可以放棄并繼續執行其他操作。
使用場景:當你不希望線程因等待鎖而無限期阻塞時使用。
特點:適用于讀多寫少且需要超時機制的場景;提供超時嘗試獲取共享鎖的方法。
示例如下:
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <chrono>std::shared_timed_mutex shtmmtx;void try_read_shared(int id) {if (shtmmtx.try_lock_shared_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is reading" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock_shared();} else {std::cout << "Thread " << id << " could not read" << std::endl;}
}void try_write_shared(int id) {if (shtmmtx.try_lock_for(std::chrono::milliseconds(100))) {std::cout << "Thread " << id << " is writing" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(50));shtmmtx.unlock();} else {std::cout << "Thread " << id << " could not write" << std::endl;}
}int main() {std::thread readers[5], writer(try_write_shared, 1);for (int i = 0; i < 5; ++i) {readers[i] = std::thread(try_read_shared, i + 2);}writer.join();for (auto& reader : readers) {reader.join();}return 0;
}
7.自旋鎖
含義:在C++中,自旋鎖(spinlock)是一種低級的同步機制,用于保護共享資源,防止多個線程同時訪問。與互斥鎖(mutex)不同,當自旋鎖被鎖定時,嘗試獲取鎖的線程會不斷循環檢查鎖是否可用,而不是進入睡眠狀態等待鎖被釋放。這意味著,自旋鎖在等待時間很短的情況下是非常有效的,但如果等待時間過長,會導致CPU資源的浪費。
C++標準庫本身并不直接提供自旋鎖的實現,但你可以使用<atomic>
庫中的原子操作來手動實現一個自旋鎖,或者使用特定平臺提供的API(如Windows的SRWLOCK
或POSIX的pthread_spinlock_t
)。
使用場景:自旋鎖適用于鎖持有時間非常短且線程不希望在操作系統調度中頻繁上下文切換的場景。這通常用在低延遲系統中,或者當線程數量不多于CPU核心數量時,確保CPU不會在等待鎖時空閑。
示例如下:
#include <atomic>
#include <iostream>
#include <thread>
#include <chrono> class Spinlock {
private: std::atomic_flag lock_ = ATOMIC_FLAG_INIT; public: void lock() { while (lock_.test_and_set(std::memory_order_acquire)) { // 循環直到鎖被釋放 } } void unlock() { lock_.clear(std::memory_order_release); } bool try_lock() { return !lock_.test_and_set(std::memory_order_acquire); }
}; void threadFunction(Spinlock& lock, int id) { lock.lock(); std::cout << "Thread " << id << " entered critical section\n"; std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模擬耗時操作 std::cout << "Thread " << id << " leaving critical section\n"; lock.unlock();
} int main() { Spinlock lock; std::thread t1(threadFunction, std::ref(lock), 1); std::thread t2(threadFunction, std::ref(lock), 2); t1.join(); t2.join(); return 0;
}
????????在這個例子中,Spinlock
?類使用了一個?std::atomic_flag
?類型的成員變量?lock_
?來實現鎖的功能。lock_
?的?test_and_set
?方法會嘗試將標志設置為?true
?并返回之前的值。如果返回?false
,表示鎖之前未被鎖定,當前線程成功獲取鎖;如果返回?true
,表示鎖已被其他線程持有,當前線程需要繼續循環等待。
????????請注意,自旋鎖在多核處理器上且等待時間較短時通常表現良好,但在等待時間較長或鎖競爭激烈時可能會導致性能問題。因此,在選擇使用自旋鎖時,需要根據具體的應用場景和性能要求做出合理的選擇。
8.總結
????????C++標準庫提供了多種類型的互斥鎖,每種鎖都有其特定的用途和特點。選擇合適的互斥鎖類型可以有效提高程序的并發性能和安全性。
C++慣用法之RAII思想: 資源管理_raii 思想-CSDN博客