亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++實現(xiàn)多線程并發(fā)場景下的同步方法

 更新時間:2025年02月18日 10:06:14   作者:kucupung  
在C++中實現(xiàn)多線程并發(fā)場景下的同步方法,包括使用互斥鎖、獨占鎖、共享鎖和原子操作的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下

如果在多線程程序中對全局變量的訪問沒有進行適當?shù)耐娇刂疲ɡ缡褂没コ怄i、原子變量等),會導致多個線程同時訪問和修改全局變量時發(fā)生競態(tài)條件(race condition)。這種競態(tài)條件可能會導致一系列不確定和嚴重的后果。

在C++中,可以通過使用互斥鎖(mutex)、原子操作、讀寫鎖來實現(xiàn)對全局變量的互斥訪問。

一、缺乏同步控制造成的后果

1. 數(shù)據(jù)競爭(Data Race)

數(shù)據(jù)競爭發(fā)生在多個線程同時訪問同一個變量,并且至少有一個線程在寫該變量時沒有進行同步。由于缺少同步機制,多個線程對全局變量的操作可能會相互干擾,導致變量的值不可預測。

示例:

#include <iostream>
#include <thread>

int globalVar = 0;

void increment() {
    globalVar++;
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();

    std::cout << "Global variable: " << globalVar << std::endl;
    return 0;
}

后果:

  • 上面的代碼中,globalVar++ 并不是一個原子操作。它由多個步驟組成:讀取值、增加值、寫回。在這段代碼中,t1 和 t2 可能會同時讀取globalVar的值,導致兩個線程同時修改它的值,最終的結果會小于預期的2。這就是典型的數(shù)據(jù)競爭。

2. 不一致的狀態(tài)(Inconsistent State)

在沒有同步控制的情況下,多個線程可能會對全局變量進行同時讀寫操作,導致變量處于不一致的狀態(tài)。例如,多個線程可能會同時讀取和修改相同的變量,導致最終狀態(tài)不符合預期。

示例: 假設你有一個程序要求維護一個全局的計數(shù)器。如果沒有加鎖來確保線程安全,兩個線程同時執(zhí)行時,計數(shù)器可能會被寫成一個無意義的值。

#include <iostream>
#include <thread>

int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        counter++;  // 非線程安全操作
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter << std::endl;
    return 0;
}

后果:

  • 在沒有同步的情況下,counter++ 可能會導致多個線程在同一時刻讀取到相同的計數(shù)器值,并同時將相同的更新值寫回變量,這會使得counter的最終值遠小于預期的200000
  • 這可能會導致程序的業(yè)務邏輯錯誤,特別是如果全局變量用作關鍵狀態(tài)的標識。

3. 崩潰或程序未定義行為

由于數(shù)據(jù)競爭或者不一致的狀態(tài),程序可能會進入一個不可預測的狀態(tài),導致崩潰。全局變量的值在多線程的競爭中可能會發(fā)生損壞,從而導致未定義的行為(undefined behavior)。

例如:

  • 訪問已釋放內存:一個線程修改了全局變量并釋放了相關內存,但其他線程仍然試圖訪問該內存。
  • 內存覆蓋:多個線程同時修改全局變量,導致不同線程的操作互相覆蓋,從而引發(fā)崩潰。

二、互斥鎖std::mutex實現(xiàn)同步

std::mutex 是C++標準庫中的一種機制,用于避免多個線程同時訪問同一個資源(如全局變量)時發(fā)生競爭條件。

下面是一個示例,展示了如何使用std::mutex來保護全局變量:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;  // 定義全局互斥鎖
int globalVar = 0;  // 定義全局變量

void threadFunction() {
    std::lock_guard<std::mutex> lock(mtx);  // 上鎖,確保互斥
    // 訪問和修改全局變量
    ++globalVar;
    std::cout << "Global variable: " << globalVar << std::endl;
    // 鎖會在lock_guard離開作用域時自動釋放
}

int main() {
    std::thread t1(threadFunction);
    std::thread t2(threadFunction);

    t1.join();
    t2.join();

    return 0;
}

說明:

  • std::mutex: 用于保護共享資源(如全局變量)。
  • std::lock_guard<std::mutex>: 是一個RAII風格的封裝器,它在構造時自動上鎖,在析構時自動解鎖,確保了線程安全。
  • threadFunction中,每個線程在訪問globalVar之前都會先獲得互斥鎖,這樣就能確保線程之間不會同時訪問和修改全局變量。

使用std::mutex可以防止不同線程之間因競爭訪問全局變量而引發(fā)的錯誤或不一致問題。

有時如果你需要更細粒度的控制,還可以考慮使用std::unique_lock,它比std::lock_guard更靈活,允許手動控制鎖的獲取和釋放。

三、獨占鎖std::unique_lock實現(xiàn)同步

std::unique_lock 是 C++11 標準庫中的一種互斥鎖包裝器,它提供了比 std::lock_guard 更靈活的鎖管理方式。std::unique_lock 允許手動控制鎖的獲取和釋放,而不僅僅是在對象生命周期結束時自動釋放鎖(如 std::lock_guard 所做的那樣)。這使得它比 std::lock_guard 更加靈活,適用于更復雜的場景,比如需要在同一作用域內多次鎖定或解鎖,或者需要在鎖定期間進行一些其他操作。

std::unique_lock 的關鍵特性:

  • 手動控制鎖的獲取和釋放:std::unique_lock 支持手動解鎖和重新鎖定,它比 std::lock_guard 更加靈活。
  • 延遲鎖定和提前解鎖:你可以選擇在對象創(chuàng)建時延遲鎖定,或者在鎖定后手動釋放鎖。
  • 支持條件變量:std::unique_lock 支持與條件變量一起使用,這是 std::lock_guard 無法做到的。

基本用法:

1. 構造時自動加鎖:

std::unique_lock 默認會在構造時自動加鎖。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void threadFunction() {
    std::unique_lock<std::mutex> lock(mtx);  // 構造時自動上鎖
    std::cout << "Thread is running\n";
    // 臨界區(qū)的操作
    // 鎖會在 lock 對象超出作用域時自動釋放
}

int main() {
    std::thread t1(threadFunction);
    std::thread t2(threadFunction);

    t1.join();
    t2.join();

    return 0;
}

2. 手動解鎖與重新加鎖:

std::unique_lock 允許你在鎖定期間手動解鎖和重新加鎖,這對于一些需要臨時釋放鎖的場景非常有用。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void threadFunction() {
    std::unique_lock<std::mutex> lock(mtx);  // 構造時自動上鎖
    std::cout << "Thread is running\n";
    
    // 臨界區(qū)的操作
    lock.unlock();  // 手動解鎖
    
    std::cout << "Lock released temporarily\n";
    
    // 臨界區(qū)之外的操作
    
    lock.lock();  // 重新加鎖
    
    std::cout << "Lock acquired again\n";
    // 臨界區(qū)操作繼續(xù)進行
}

int main() {
    std::thread t1(threadFunction);
    std::thread t2(threadFunction);

    t1.join();
    t2.join();

    return 0;
}

3. 延遲鎖定:

std::unique_lock 也允許你延遲鎖定,通過傳遞一個 std::defer_lock 參數(shù)給構造函數(shù)來實現(xiàn)。這會創(chuàng)建一個未鎖定的 std::unique_lock,你可以在稍后手動調用 lock() 來加鎖。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void threadFunction() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 延遲加鎖
    std::cout << "Thread is preparing to run\n";
    
    // 做一些不需要加鎖的操作
    
    lock.lock();  // 手動加鎖
    std::cout << "Thread is running under lock\n";
    
    // 臨界區(qū)的操作
}

int main() {
    std::thread t1(threadFunction);
    std::thread t2(threadFunction);

    t1.join();
    t2.join();

    return 0;
}

4. 條件變量:

std::unique_lock 是與條件變量一起使用的理想選擇,它支持對互斥鎖的手動解鎖和重新加鎖。這在條件變量的使用場景中非常有用,因為在等待條件時需要解鎖互斥鎖,而在條件滿足時重新加鎖。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void threadFunction() {
    std::unique_lock<std::mutex> lock(mtx);  // 上鎖
    while (!ready) {  // 等待 ready 為 true
        cv.wait(lock);  // 等待,自動解鎖并掛起線程
    }
    std::cout << "Thread is running\n";
}

void notify() {
    std::this_thread::sleep_for(std::chrono::seconds(1));  // 模擬一些操作
    std::cout << "Notifying the threads\n";
    std::unique_lock<std::mutex> lock(mtx);  // 上鎖
    ready = true;
    cv.notify_all();  // 通知所有線程
}

int main() {
    std::thread t1(threadFunction);
    std::thread t2(threadFunction);

    std::thread notifier(notify);
    
    t1.join();
    t2.join();
    notifier.join();

    return 0;
}

解釋:

  • std::condition_variable 和 std::unique_lock

    • 在 threadFunction 中,cv.wait(lock) 會釋放鎖并等待條件變量的通知。
    • std::unique_lock 能夠在調用 wait 時自動釋放鎖,并且在 wait 返回時會重新加鎖,這使得 std::unique_lock 成為使用條件變量的最佳選擇。
  • cv.notify_all():通知所有等待該條件的線程,thread1 和 thread2 都會在條件滿足時繼續(xù)執(zhí)行。

四、共享鎖std::shared_mutex實現(xiàn)同步

std::shared_mutex 是 C++17 引入的一個同步原語,它提供了一種讀寫鎖機制,允許多個線程共享讀取同一資源,而只有一個線程能夠獨占寫入該資源。相比于傳統(tǒng)的 std::mutex(只支持獨占鎖),std::shared_mutex 可以提高并發(fā)性,特別是在讀操作遠多于寫操作的情況下。

std::shared_mutex 的工作原理:

  • 共享鎖(shared lock):多個線程可以同時獲取共享鎖,這意味著多個線程可以同時讀取共享資源。多個線程獲取共享鎖時不會發(fā)生沖突。
  • 獨占鎖(unique lock):只有一個線程可以獲取獨占鎖,這意味著寫操作會阻塞其他所有操作(無論是讀操作還是寫操作),以保證數(shù)據(jù)的一致性。

使用 std::shared_mutex:

std::shared_mutex 提供了兩種類型的鎖:

  • std::unique_lock<std::shared_mutex>:用于獲取獨占鎖。
  • std::shared_lock<std::shared_mutex>:用于獲取共享鎖。

1. 基本使用示例:

#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>

std::shared_mutex mtx;  // 定義一個 shared_mutex
int sharedData = 0;

void readData(int threadId) {
    std::shared_lock<std::shared_mutex> lock(mtx);  // 獲取共享鎖
    std::cout << "Thread " << threadId << " is reading data: " << sharedData << std::endl;
}

void writeData(int threadId, int value) {
    std::unique_lock<std::shared_mutex> lock(mtx);  // 獲取獨占鎖
    sharedData = value;
    std::cout << "Thread " << threadId << " is writing data: " << sharedData << std::endl;
}

int main() {
    std::vector<std::thread> threads;

    // 啟動多個線程進行讀取操作
    for (int i = 0; i < 5; ++i) {
        threads.push_back(std::thread(readData, i));
    }

    // 啟動一個線程進行寫入操作
    threads.push_back(std::thread(writeData, 100, 42));

    // 等待所有線程結束
    for (auto& t : threads) {
        t.join();
    }

    return 0;
}

解釋:

  • 共享鎖 (std::shared_lock):線程 readData 使用 std::shared_lock 獲取共享鎖,這允許多個線程同時讀取 sharedData,因為讀取操作是線程安全的。
  • 獨占鎖 (std::unique_lock):線程 writeData 使用 std::unique_lock 獲取獨占鎖,這確保了只有一個線程可以寫 sharedData,并且寫操作會阻塞所有其他線程(包括讀操作和寫操作)。

2. 多個讀線程與單個寫線程的并發(fā)控制:

在這個示例中,多個讀線程可以并行執(zhí)行,因為它們都獲取了共享鎖。只有當寫線程(獲取獨占鎖)執(zhí)行時,其他線程(無論是讀線程還是寫線程)會被阻塞。

  • 寫操作:獲取獨占鎖,所有讀操作和寫操作都會被阻塞,直到寫操作完成。
  • 讀操作:多個線程可以同時獲取共享鎖,只有在沒有寫操作時才會執(zhí)行。

3. 共享鎖與獨占鎖的沖突:

  • 共享鎖:多個線程可以同時獲取共享鎖,只要沒有線程持有獨占鎖。共享鎖不會阻塞其他共享鎖請求。
  • 獨占鎖:當一個線程持有獨占鎖時,其他任何線程的共享鎖或獨占鎖請求都會被阻塞,直到獨占鎖釋放。

4. 使用場景:

std::shared_mutex 主要適用于讀多寫少的場景。假設有一個資源(如緩存、數(shù)據(jù)結構),它在大部分時間內被多個線程讀取,但偶爾需要被更新。在這種情況下,std::shared_mutex 可以讓多個讀操作并行執(zhí)行,同時避免寫操作導致的不必要的阻塞。

例如:

  • 緩存數(shù)據(jù)讀取:多個線程可以并發(fā)讀取緩存中的數(shù)據(jù),而當緩存需要更新時,獨占鎖會確保數(shù)據(jù)一致性。
  • 數(shù)據(jù)庫的并發(fā)查詢和修改:多個線程可以并發(fā)查詢數(shù)據(jù)庫,但只有一個線程可以執(zhí)行寫操作。

5. std::shared_mutex 與 std::mutex 比較:

  • std::mutex:提供獨占鎖,適用于寫操作頻繁且不需要并發(fā)讀的場景。每次加鎖時,其他線程都無法進入臨界區(qū)。
  • std::shared_mutex:適用于讀多寫少的場景,允許多個線程同時讀取共享資源,但寫操作會阻塞所有其他操作。

6. 性能考慮:

  • 讀操作頻繁時:使用 std::shared_mutex 可以提高并發(fā)性,因為多個線程可以同時讀取數(shù)據(jù)。
  • 寫操作頻繁時:性能可能會低于 std::mutex,因為寫操作需要獨占資源并阻塞所有其他操作。

7. 條件變量:

與 std::mutex 一樣,std::shared_mutex 也可以與條件變量(std::condition_variable)一起使用,不過在使用時要注意,不同的線程需要加鎖和解鎖對應的鎖。

#include <iostream>
#include <thread>
#include <shared_mutex>
#include <condition_variable>

std::shared_mutex mtx;
std::condition_variable_any cv;
int sharedData = 0;

void readData() {
    std::shared_lock<std::shared_mutex> lock(mtx);  // 獲取共享鎖
    while (sharedData == 0) {  // 等待數(shù)據(jù)可用
        cv.wait(lock);  // 等待數(shù)據(jù)被寫入
    }
    std::cout << "Reading data: " << sharedData << std::endl;
}

void writeData(int value) {
    std::unique_lock<std::shared_mutex> lock(mtx);  // 獲取獨占鎖
    sharedData = value;
    std::cout << "Writing data: " << sharedData << std::endl;
    cv.notify_all();  // 通知所有等待的線程
}

int main() {
    std::thread reader(readData);
    std::thread writer(writeData, 42);

    reader.join();
    writer.join();

    return 0;
}

解釋:

  • std::shared_lock:用于共享讀鎖,允許多個線程同時讀取。
  • cv.wait(lock):使用共享鎖來等待某些條件的變化。
  • cv.notify_all():通知所有等待線程,喚醒它們繼續(xù)執(zhí)行。

五、std::atomic實現(xiàn)同步

std::atomic 是 C++11 標準引入的一種類型,用于實現(xiàn)原子操作。原子操作指的是操作在執(zhí)行過程中不可被中斷,因此能夠保證數(shù)據(jù)的一致性和正確性。

std::atomic 提供了一些基本的原子操作方法,這些操作是不可分割的,保證了在多線程環(huán)境下線程安全。它主要用于數(shù)據(jù)的同步與協(xié)作,避免了傳統(tǒng)同步原語(如鎖、條件變量)所帶來的性能瓶頸。

原子操作的基本概念:

  • 原子性:在執(zhí)行時,操作不能被打斷,保證線程之間對共享變量的操作不會產(chǎn)生競態(tài)條件。
  • 內存順序(Memory Ordering):控制操作的執(zhí)行順序和對共享數(shù)據(jù)的可見性,std::atomic 允許通過內存順序來顯式指定不同線程間的同步行為。

std::atomic 提供的原子操作:

  • 加載(Load):從原子變量中讀取數(shù)據(jù)。
  • 存儲(Store):將數(shù)據(jù)存儲到原子變量中。

std::atomic 支持的內存順序(Memory Ordering):

  • std::memory_order_acquire:確保前面的操作在加載之后執(zhí)行,即它會阻止后續(xù)的操作在此之前執(zhí)行。
  • std::memory_order_release:確保后面的操作在存儲之前執(zhí)行,即它會阻止前面的操作在此之后執(zhí)行。

通常情況下,在使用 std::atomic 進行同步時,使用 memory_order_release 在 store 操作時,使用 memory_order_acquire 在 load 操作時,是一種常見的模式,特別是在生產(chǎn)者-消費者模式或者其他類似的同步模式下。

memory_order_release 和 memory_order_acquire 一般搭配使用。

這種組合是為了確保 內存順序的一致性,并且保證數(shù)據(jù)正確的可見性。具體來說:

  • memory_order_release:在執(zhí)行 store 操作時,它會確保在 store 之前的所有操作(如數(shù)據(jù)寫入)不會被重排序到 store 之后,保證當前線程的寫操作對其他線程是可見的。因此,store 操作保證所有前置的寫操作都會在這個 store 完成后被其他線程看到。

  • memory_order_acquire:在執(zhí)行 load 操作時,它會確保在 load 之后的所有操作(如數(shù)據(jù)讀?。┎粫恢嘏判虻?nbsp;load 之前,保證當前線程在讀取共享數(shù)據(jù)后,后續(xù)的操作可以看到正確的數(shù)據(jù)。在 load 之前的所有操作(包括對共享變量的寫入)會在讀取這個值之后對當前線程可見。

這兩者配合使用,確保了線程間的同步,避免了數(shù)據(jù)競態(tài)條件。

具體場景

考慮一個生產(chǎn)者-消費者模型,生產(chǎn)者負責寫入數(shù)據(jù)并通知消費者,消費者負責讀取數(shù)據(jù)并處理。

示例:

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> data(0);
std::atomic<bool> ready(false);

void consumer() {
    while (!ready.load(std::memory_order_acquire)) {
        // 等待 ready 為 true
    }
    std::cout << "Data: " << data.load(std::memory_order_relaxed) << std::endl;
}

void producer() {
    data.store(42, std::memory_order_relaxed);  // 寫數(shù)據(jù)
    ready.store(true, std::memory_order_release);  // 設置 ready 為 true
}

int main() {
    std::thread t1(consumer);
    std::thread t2(producer);

    t1.join();
    t2.join();

    return 0;
}

解釋:

  • ready.store(true, std::memory_order_release):生產(chǎn)者線程在寫入 ready 時使用 memory_order_release,這意味著在 ready 設置為 true 之后,所有在此之前的操作(如對 data 的寫入)對消費者線程是可見的。

  • ready.load(std::memory_order_acquire):消費者線程在讀取 ready 時使用 memory_order_acquire,這意味著消費者線程在讀取 ready 后,確保它能夠看到生產(chǎn)者線程在 store ready 之前所做的所有修改(如 data 的值)。

這種組合保證了生產(chǎn)者線程的寫操作(例如 data.store(42))對于消費者線程是可見的,且在讀取 ready 后,消費者線程可以安全地讀取到更新后的 data。

到此這篇關于C++實現(xiàn)多線程并發(fā)場景下的同步方法的文章就介紹到這了,更多相關C++ 多線程并發(fā)同步內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C++中STL的常用算法總結

    C++中STL的常用算法總結

    這篇文章主要介紹了C++?STL中一些常見算法的使用,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-12-12
  • C++構造函數(shù)+復制構造函數(shù)+重載等號運算符調用

    C++構造函數(shù)+復制構造函數(shù)+重載等號運算符調用

    這篇文章主要介紹了C++構造函數(shù)+復制構造函數(shù)+重載等號運算符調用,文章敘述詳細,具有一定的的參考價值,需要的小伙伴可以參考一下
    2022-03-03
  • C++使用動態(tài)內存分配的原因解說

    C++使用動態(tài)內存分配的原因解說

    這篇文章主要介紹了C++使用動態(tài)內存分配的原因解說,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • C語言進階之字符串查找?guī)旌瘮?shù)詳解

    C語言進階之字符串查找?guī)旌瘮?shù)詳解

    字符串是一種非常重要的數(shù)據(jù)類型,但是C語言不存在顯式的字符串類型,C語言中的字符串都以字符串常量的形式出現(xiàn)或存儲在字符數(shù)組中,下面這篇文章主要給大家介紹了關于C語言進階之字符串查找?guī)旌瘮?shù)的相關資料,需要的朋友可以參考下
    2023-01-01
  • C語言實現(xiàn)牛頓迭代法解方程詳解

    C語言實現(xiàn)牛頓迭代法解方程詳解

    這篇文章主要介紹了C語言實現(xiàn)牛頓迭代法解方程詳解的相關資料,需要的朋友可以參考下
    2017-03-03
  • C++編程中的或||、與&&、非!邏輯運算符基本用法整理

    C++編程中的或||、與&&、非!邏輯運算符基本用法整理

    這篇文章主要介紹了C++中的或||、與&&、非!邏輯運算符基本用法整理,是C++入門學習中的基礎知識,需要的朋友可以參考下
    2016-01-01
  • FFmpeg實現(xiàn)變速播放的兩種方法總結

    FFmpeg實現(xiàn)變速播放的兩種方法總結

    這篇文章主要為大家詳細介紹了FFmpeg中實現(xiàn)變速播放的兩種方法,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的可以了解一下
    2023-07-07
  • C語言一看就懂的選擇與循環(huán)語句及函數(shù)介紹

    C語言一看就懂的選擇與循環(huán)語句及函數(shù)介紹

    函數(shù)是一個功能模塊,它把實現(xiàn)某個功能的代碼塊包含起來,并起一個函數(shù)名,供別人調用,如printf函數(shù),如system函數(shù)。是程序運行當中包裝起來的一個步驟;選擇與循環(huán)是編程中最常用的結構,本篇文章用最簡單的文字帶你了解它們
    2022-04-04
  • C語言利用鏈表與文件實現(xiàn)登錄注冊功能

    C語言利用鏈表與文件實現(xiàn)登錄注冊功能

    這篇文章主要介紹了C語言利用鏈表與文件實現(xiàn)登錄注冊功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • 深入理解C++內鏈接與外鏈接的意義

    深入理解C++內鏈接與外鏈接的意義

    鏈接描述了名稱在整個程序或一個翻譯單元中如何引用或不引用同一實體,下面這篇文章主要給大家介紹了關于C++內鏈接與外鏈接意義的理解,需要的朋友可以參考下
    2021-11-11

最新評論