首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++0x 多線程編程(三):初始化同步與條件變量實戰

     編輯:關於C++

C++0x(即後來的 C++11)在多線程支援上引入了豐富的機制。本文聚焦於初始化階段的資料保護與線程間事件等待兩大核心場景。

一、初始化時保護共享資料

若資料需在初始化時受保護,使用 mutex 會導致初始化結束後仍產生不必要的同步開銷。C++0x 提供三種更優雅的方案:

1. constexpr 建構函數 + 靜態儲存期

若建構函數以 constexpr 宣告並滿足常量初始化條件,靜態儲存期物件會在靜態初始化階段完成,早於任何其他代碼執行。這對 std::mutex 尤其適合,消除了全域 mutex 初始化競爭的可能:

class my_class {
    int i;
public:
    constexpr my_class() : i(0) {}
    my_class(int i_) : i(i_) {}
    void do_stuff();
};

my_class x;                 // 靜態初始化,安全
my_class y(42 + foo());     // 動態初始化,需注意

void f() { y.do_stuff(); }  // y 是否已初始化?

2. 區塊作用域靜態變量

C++0x 保證區塊作用域的靜態變量在函數首次被呼叫時初始化,若另一線程在初始化完成前嘗試呼叫,會自動等待:

void bar() {
    static my_class z(42 + foo()); // 初始化過程線程安全
    z.do_stuff();
}

3. std::call_once + std::once_flag

適用於動態建立物件的場景。std::call_once 與 std::once_flag 協作,確保指定函數只執行一次:

my_class* p = nullptr;
std::once_flag p_flag;

void create_instance() { p = new my_class(42 + foo()); }

void baz() {
    std::call_once(p_flag, create_instance);
    p->do_stuff();
}

std::call_once 同樣支援函數物件與多參數,預設傳值,需傳引用時使用 std::ref。

二、等待事件:條件變量優於輪詢

線程間共享資料時,常需一個線程等待另一個線程完成某操作。最簡單的輪詢寫法(睡眠 + 檢查旗標)有兩大缺點:

  1. 平均延遲半個睡眠週期(如 5ms)。
  2. 頻繁喚醒、鎖定、檢查,浪費 CPU 並增加 mutex 爭用。

條件變量(condition_variable) 讓線程高效休眠直到收到通知,CPU 佔用率可降至 0:

std::mutex m;
std::condition_variable cond;
bool data_ready = false;

void foo() {
    std::unique_lock<std::mutex> lk(m);
    while (!data_ready) {
        cond.wait(lk); // 釋放鎖並休眠,被喚醒後重新獲取鎖
    }
    process_data();
}

void set_data_ready() {
    std::lock_guard<std::mutex> lk(m);
    data_ready = true;
    cond.notify_one(); // 喚醒一個等待線程
}

注意:條件變量可能發生虛假喚醒,因此必須在迴圈中檢查條件。更簡潔的寫法是使用帶 predicate 的 wait:

void foo() {
    std::unique_lock<std::mutex> lk(m);
    cond.wait(lk, []{ return data_ready; }); // 標準庫自動處理虛假喚醒
    process_data();
}

掌握這些機制,能讓多線程程式在初始化與同步協作上更安全、高效、優雅。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved