C++多線程與鎖機制使用解讀
1. 基本多線程編程
1.1 創(chuàng)建線程
#include <iostream>
#include <thread>
void thread_function() {
std::cout << "Hello from thread!\n";
}
int main() {
std::thread t(thread_function); // 創(chuàng)建并啟動線程
t.join(); // 等待線程結(jié)束
return 0;
}1.2 帶參數(shù)的線程函數(shù)
#include <thread>
#include <iostream>
void print_num(int num) {
std::cout << "Number: " << num << "\n";
}
int main() {
std::thread t(print_num, 42);
t.join();
return 0;
}1.3 join() 和 detach()
std::thread t(threadFunction);
// join() - 等待線程完成
t.join();
// detach() - 分離線程,線程獨立運行
// t.detach();
// 檢查線程是否可joinable
if (t.joinable()) {
t.join();
}1.4 獲取當前線程信息
#include <thread>
#include <iostream>
int main() {
std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;
std::thread t([](){
std::cout << "Worker thread ID: " << std::this_thread::get_id() << std::endl;
});
t.join();
return 0;
}1.5 線程休眠
#include <chrono>
#include <thread>
int main() {
std::cout << "Sleeping for 2 seconds..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Awake!" << std::endl;
return 0;
}2. mutex (互斥鎖)
#include <thread>
#include <mutex>
#include <iostream>
std::mutex mtx; // 全局互斥鎖
int shared_data = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock(); // 上鎖
++shared_data;
mtx.unlock(); // 解鎖
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final value: " << shared_data << "\n";
return 0;
}2.1 lock_guard (自動管理鎖)
lock_guard 在構(gòu)造時自動上鎖,在析構(gòu)時自動解鎖,防止忘記解鎖
void increment_safe() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 自動上鎖
++shared_data;
} // 自動解鎖
}2.2 unique_lock
unique_lock 比 lock_guard 更靈活,可以手動上鎖和解鎖。
void increment_flexible() {
for (int i = 0; i < 100000; ++i) {
std::unique_lock<std::mutex> lock(mtx);
++shared_data;
lock.unlock(); // 可以手動解鎖
// 做一些不需要鎖的操作
lock.lock(); // 再手動上鎖
++shared_data;
}
}2.3 嘗試鎖try_lock()
void tryLockExample() {
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
// 成功獲取鎖
std::cout << "Got the lock!\n";
} else {
// 未能獲取鎖
std::cout << "Couldn't get the lock, doing something else...\n";
}
}2.4 遞歸互斥鎖std::recursive_mutex
#include <mutex>
std::recursive_mutex rec_mtx;
void recursiveFunction(int count) {
std::lock_guard<std::recursive_mutex> lock(rec_mtx);
if (count > 0) {
std::cout << "Count: " << count << '\n';
recursiveFunction(count - 1);
}
}
int main() {
std::thread t(recursiveFunction, 3);
t.join();
return 0;
}2.5 定時互斥鎖std::timed_mutex
#include <mutex>
#include <chrono>
std::timed_mutex timed_mtx;
void timedLockExample() {
auto timeout = std::chrono::milliseconds(100);
if (timed_mtx.try_lock_for(timeout)) {
// 在100ms內(nèi)成功獲取鎖
std::this_thread::sleep_for(std::chrono::milliseconds(50));
timed_mtx.unlock();
} else {
// 超時未能獲取鎖
std::cout << "Could not get the lock within 100ms\n";
}
}2.6std::adopt_lock與std::defer_lock
| 特性 | std::adopt_lock | std::defer_lock |
|---|---|---|
| 用途 | 表示鎖已被當前線程獲得 | 表示不立即獲取鎖 |
| 加鎖時機 | 不嘗試加鎖(假設(shè)已鎖定) | 稍后手動加鎖 |
| 典型使用場景 | 與 std::lock 配合使用 | 延遲加鎖或條件加鎖 |
| 可用性 | 適用于 lock_guard 和 unique_lock | 僅適用于 unique_lock |
adopt_lock表示當前線程已經(jīng)獲得了互斥鎖的所有權(quán),不需要再嘗試加鎖
#include <mutex>
std::mutex mtx;
void function() {
mtx.lock(); // 手動加鎖
// 使用 adopt_lock 告訴 lock_guard 我們已經(jīng)擁有鎖
std::lock_guard<std::mutex> lock(mtx, std::adopt_lock);
// 臨界區(qū)代碼...
// 離開作用域時自動解鎖
}3. 條件變量 (condition_variable)
用于線程間的同步,允許線程等待特定條件成立。
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // 等待ready變?yōu)閠rue
std::cout << "Worker is processing data\n";
}
int main() {
std::thread t(worker);
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 通知等待的線程
t.join();
return 0;
}3.1wait
std::unique_lock<std::mutex> lock(mtx); cv.wait(lock); // 無條件等待,可能虛假喚醒
帶謂詞的 wait()
cv.wait(lock, []{ return ready; }); // 等價于:
// while (!ready) {
// cv.wait(lock);
// }wait_for() - 帶超時等待
using namespace std::chrono_literals;
if (cv.wait_for(lock, 100ms, []{ return ready; })) {
// 條件在超時前滿足
} else {
// 超時
}wait_until() - 等待到指定時間點
auto timeout = std::chrono::steady_clock::now() + 100ms;
if (cv.wait_until(lock, timeout, []{ return ready; })) {
// 條件在時間點前滿足
} else {
// 超時
}3.2notify
notify_one() - 通知一個等待線程
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // 只喚醒一個等待線程notify_all() - 通知所有等待線程
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_all(); // 喚醒所有等待線程3.3 生產(chǎn)消費者模式示例
#include <queue>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
const int MAX_SIZE = 10;
void producer() {
for (int i = 0; i < 20; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return data_queue.size() < MAX_SIZE; });
data_queue.push(i);
std::cout << "Produced: " << i << std::endl;
lock.unlock();
cv.notify_all();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !data_queue.empty(); });
int data = data_queue.front();
data_queue.pop();
std::cout << "Consumed: " << data << std::endl;
lock.unlock();
cv.notify_all();
if (data == 19) break; // 結(jié)束條件
}
}
int main() {
std::thread p(producer);
std::thread c(consumer);
p.join();
c.join();
return 0;
}4. 原子操作 (atomic)
對于簡單的數(shù)據(jù)類型,可以使用原子操作避免鎖的開銷。
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> counter(0);
void increment_atomic() {
for (int i = 0; i < 100000; ++i) {
++counter; // 原子操作,無需鎖
}
}
int main() {
std::thread t1(increment_atomic);
std::thread t2(increment_atomic);
t1.join();
t2.join();
std::cout << "Counter: " << counter << "\n";
return 0;
}4.1 基本原子類型
#include <atomic> std::atomic<int> atomicInt(0); // 原子整數(shù) std::atomic<bool> atomicBool(false); // 原子布爾值 std::atomic<long> atomicLong; // 默認初始化為0
4.2 加載和存儲
// 存儲值 atomicInt.store(42); // 原子存儲 atomicInt = 42; // 等價寫法 // 加載值 int value = atomicInt.load(); // 原子加載 value = atomicInt; // 等價寫法
4.3 交換操作
int old = atomicInt.exchange(100); // 原子交換為新值,返回舊值
4.4 讀-修改-寫操作
std::atomic<int> counter(0); // 原子加法,返回舊值 int prev = counter.fetch_add(5); // counter += 5,返回加前的值 // 原子減法 prev = counter.fetch_sub(3); // counter -= 3,返回減前的值
std::atomic<int> flags(0); flags.fetch_or(0x01); // 原子按位或 flags.fetch_and(~0x01); // 原子按位與 flags.fetch_xor(0x03); // 原子按位異或
4.5 比較交換 (CAS)
std::atomic<int> value(10); int expected = 10; // 比較并交換 bool success = value.compare_exchange_weak(expected, 20); // 如果value == expected,則設(shè)置為20,返回true // 否則將expected更新為當前value,返回false // 強版本 (較少虛假失敗) success = value.compare_exchange_strong(expected, 30);
4.6 內(nèi)存順序 (Memory Order)
// 默認是最嚴格的內(nèi)存順序 (sequential consistency) atomicInt.store(42, std::memory_order_seq_cst); // 寬松內(nèi)存順序 atomicInt.store(42, std::memory_order_relaxed); // 常見內(nèi)存順序: // - memory_order_relaxed: 無順序保證 // - memory_order_consume: 數(shù)據(jù)依賴順序 // - memory_order_acquire: 讀操作,防止上方讀寫重排 // - memory_order_release: 寫操作,防止下方讀寫重排 // - memory_order_acq_rel: 讀-修改-寫操作 // - memory_order_seq_cst: 順序一致性 (默認)
4.7 原子標志
std::atomic_flag flag = ATOMIC_FLAG_INIT; // 必須這樣初始化 // 測試并設(shè)置 (原子操作) bool was_set = flag.test_and_set(); // 清除標志 flag.clear();
4.8 原子指針
class MyClass {};
MyClass* ptr = new MyClass();
std::atomic<MyClass*> atomicPtr(ptr);
// 原子指針操作
MyClass* old = atomicPtr.exchange(new MyClass());
// 比較交換指針
MyClass* expected = old;
atomicPtr.compare_exchange_strong(expected, nullptr);4.9 自定義原子類型
struct Point { int x; int y; };
std::atomic<Point> atomicPoint{Point{1, 2}};
// 必須是可平凡復制的類型(trivially copyable)
static_assert(std::is_trivially_copyable<Point>::value,
"Point must be trivially copyable");
// 原子操作示例
Point old = atomicPoint.load(); // 原子讀取
atomicPoint.store(Point{3, 4}); // 原子寫入
Point newVal{5, 6};
Point expected{3, 4};
atomicPoint.compare_exchange_strong(expected, newVal); // CAS操作std::atomic 對模板類型 T 的關(guān)鍵要求是:
- 可平凡復制(Trivially Copyable):保證對象可以用
memcpy方式安全復制 - 無用戶定義的拷貝控制(析構(gòu)函數(shù)、拷貝/移動構(gòu)造/賦值)
- 標準布局(Standard Layout)
static_assert 在編譯時驗證這些條件,若不滿足會立即報錯(比運行時錯誤更安全)。
一個類型 T 是 平凡可復制(Trivially Copyable) 的,當且僅當滿足以下所有條件:
- 沒有用戶定義的拷貝構(gòu)造函數(shù)(
T(const T&)) - 沒有用戶定義的移動構(gòu)造函數(shù)(
T(T&&)) - 沒有用戶定義的拷貝賦值運算符(
T& operator=(const T&)) - 沒有用戶定義的移動賦值運算符(
T& operator=(T&&)) - 有一個平凡的(隱式定義的或
=default)析構(gòu)函數(shù) - 所有非靜態(tài)成員和基類也必須是平凡可復制的
- 不能有虛函數(shù)或虛基類
如果滿足這些條件,編譯器可以安全地使用 memcpy 來復制該類型的對象,而不會引發(fā)未定義行為(UB)。
5. 死鎖預防
當多個線程需要多個鎖時,可能產(chǎn)生死鎖。
預防方法:
- 總是以相同的順序獲取鎖
- 使用
std::lock同時鎖定多個互斥量
std::mutex mtx1, mtx2;
void safe_lock() {
// 同時鎖定兩個互斥量,避免死鎖
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
// 安全地訪問共享資源
}6. 線程局部存儲 (thread_local)
使用 thread_local 關(guān)鍵字聲明線程局部變量,每個線程有自己的副本。
#include <thread>
#include <iostream>
thread_local int thread_specific_value = 0;
void thread_function(int id) {
thread_specific_value = id;
std::cout << "Thread " << id << ": " << thread_specific_value << "\n";
}
int main() {
std::thread t1(thread_function, 1);
std::thread t2(thread_function, 2);
t1.join();
t2.join();
return 0;
}7. 讀寫鎖
讀寫鎖是一種特殊的同步機制,允許多個讀操作并發(fā)執(zhí)行,但寫操作必須獨占訪問。這種鎖在"讀多寫少"的場景下能顯著提高性能。
C++17 中的std::shared_mutex
#include <shared_mutex>
#include <vector>
class ThreadSafeContainer {
private:
std::vector<int> data;
mutable std::shared_mutex mutex; // mutable 允許const方法加鎖
public:
// 讀操作 - 使用共享鎖
int get(size_t index) const {
std::shared_lock<std::shared_mutex> lock(mutex);
return data.at(index);
}
// 寫操作 - 使用獨占鎖
void set(size_t index, int value) {
std::unique_lock<std::shared_mutex> lock(mutex);
data.at(index) = value;
}
// 批量讀操作示例
std::vector<int> getSnapshot() const {
std::shared_lock<std::shared_mutex> lock(mutex);
return data;
}
};讀寫鎖特性
三種訪問模式:
- 共享讀鎖 (
shared_lock):多個線程可同時持有 - 獨占寫鎖 (
unique_lock):只有一個線程可持有 - 升級鎖 (C++14沒有直接支持,需手動實現(xiàn))
鎖的優(yōu)先級策略:
- 讀優(yōu)先:容易導致寫線程饑餓
- 寫優(yōu)先:可能降低讀并發(fā)度
- 公平策略:折中方案
典型使用場景:
- 配置信息的熱更新
- 緩存系統(tǒng)
- 高頻查詢低頻修改的數(shù)據(jù)結(jié)構(gòu)
8. 自旋鎖 (Spin Lock)
自旋鎖是一種非阻塞鎖,當線程無法獲取鎖時不會休眠,而是循環(huán)檢查鎖狀態(tài)(忙等待)。適用于鎖持有時間極短的場景。
基本自旋鎖實現(xiàn)
#include <atomic>
class SpinLock {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while(flag.test_and_set(std::memory_order_acquire)) {
// 可加入CPU暫停指令減少爭用時的能耗
#ifdef __x86_64__
__builtin_ia32_pause();
#endif
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
bool try_lock() {
return !flag.test_and_set(std::memory_order_acquire);
}
};TTAS + Backoff
class AdvancedSpinLock {
std::atomic<bool> locked{false};
public:
void lock() {
bool expected = false;
int backoff = 1;
const int max_backoff = 64;
while(!locked.compare_exchange_weak(expected, true,
std::memory_order_acquire, std::memory_order_relaxed)) {
expected = false; // compare_exchange_weak會修改expected
// 指數(shù)退避
for(int i = 0; i < backoff; ++i) {
#ifdef __x86_64__
__builtin_ia32_pause();
#endif
}
backoff = std::min(backoff * 2, max_backoff);
}
}
void unlock() {
locked.store(false, std::memory_order_release);
}
};總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
linux環(huán)境下C++實現(xiàn)俄羅斯方塊
這篇文章主要為大家詳細介紹了linux環(huán)境下C++實現(xiàn)俄羅斯方塊,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06
C/C++中的?Qt?StandardItemModel?數(shù)據(jù)模型應用解析
QStandardItemModel?是標準的以項數(shù)據(jù)為單位的基于M/V模型的一種標準數(shù)據(jù)管理方式,本文給大家介紹C/C++中的?Qt?StandardItemModel?數(shù)據(jù)模型應用解析,感興趣的朋友跟隨小編一起看看吧2021-12-12

