目錄
- std::unique_lock類模板
- 僅調用一次
- 線程局部存儲
- 原子變量
- 往期內容回顧
std::unique_lock類模板
互斥鎖保證了線程間的同步,卻將并行操作變成了串行操作,對性能有較大影響,所以我們要盡可能減小鎖的區間粒度。
lock_guard
只能保證在析構的時候進行解鎖操作,所以其本身并沒有提供加鎖解鎖的接口
class logFile{
private:std::mutex mtx;ofstream f;
public:logFile() {f.open("log.txt");}~logFile() {f.close();}void sharedPrint(string msg, int id) {{std::lock_guard<std::mutex> guard(mtx);// do sth1}// do sth2// ...{std::lock_guard<std::mutex> guard(mtx);// do sth3f << msg << id << endl;cout << msg << id << endl;}}
};
上面代碼中,sharedPrint函數內部有兩段代碼需要進行加鎖保護,此時使用lock_guard就需要創建兩個局部對象來管理同一個互斥鎖。
其實可以使用unique_lock,它提供了**lock()和unlock()**接口,能記錄現在處于上鎖還是沒上鎖狀態,在析構的時候,會根據當前狀態來決定是否要進行解鎖(lock_guard就一定會解鎖)
void sharedPrint(string msg, int id) {std::unique_lock<std::mutex> guard(mtx);// do sth1guard.unlock(); // 臨時解鎖// do sth2guard.lock(); // 繼續上鎖// do sth3f << msg << id << endl;cout << msg << id << endl;// guard.unlock(); // 這個可要可不要,因為析構的時候也會自動執行的}
僅調用一次
程序免不了要初始化數據,線程并發沒有某種同步手段來控制,會導致初始化函數多次運行。
c++11種提供僅調用一次的功能,首先聲明一個once_flag類型的變量作為初始化標志,最好是靜態、全局的,這樣對于線程就是可見的了。然后調用專門的call_once()函數,以函數式編程的方式,傳遞這個標志和初始化函數,這樣就可以保證,即使多個線程重入call_once(),也只能由一個線程會成功初始化
#include <iostream>
#include <thread>
#include <mutex>// 全局的初始化標志
static std::once_flag flag;
auto f = []()
{std::call_once(flag,[](){std::cout << "only once" << std::endl;});
};
int main() {// 啟動兩個線程,運行函數fstd::thread t1(f);std::thread t2(f);t1.join();t2.join();// std::cout << "finished" << std::endl;return 0;
}
實際編譯結果如下:
only once
線程局部存儲
當讀寫全局變量時,一般也會出現數據競爭,但是全局變量不一定是必須共享的,可能僅僅是為了方便線程傳入傳出數據,不是共享所有權。此時可以使用關鍵字thread_local
實現線程局部緩存,被其標志的變量在每個線程里都會有一個獨立的副本。
#include <iostream>
#include <thread>int main() {// 啟動兩個線程,運行函數fthread_local int n = 0;auto f = [&](int x){n += x;std::cout << n << std::endl;};std::thread t1(f, 10);std::thread t2(f, 20);t1.join();t2.join();// std::cout << "finished" << std::endl;return 0;
}
結果是:
10
20
說明兩個線程互不干擾,如果將變量聲明改為static,兩個線程會共享這個變量,導致連續加兩次,結果是下面:
10 or 20
30 30
原子變量
對于非獨占、必須共享的數據,要保證多線程讀寫共享數據一致就要解決同步問題,兩個線程得互斥。但是mutex互斥量成本較高,可以使用atmoic原子化操作。
int main() {static std::atomic_flag flag {false}; // 原子化的標志量static atomic_int n; // 原子化的intauto f = [&](){auto value = flag.test_and_set(); // TAS檢查原子標志量if (value) {std::cout << "flag has been set" << std::endl;} else {std::cout << "set flag by" << std::this_thread::get_id() << std::endl; // 輸出線程id}n += 100; // 原子變量加法運算std::this_thread::sleep_for(std::chrono::seconds(5)); //線程睡眠std::cout << n << std::endl;};std::thread t1(f);std::thread t2(f);t1.join();t2.join();
}
往期內容回顧
C++多線程快速入門(一):基本&常用操作