標准線程庫,c++11引入,包含原子操作庫、互斥鎖、條件變量。。。
一、線程庫<thread>
創建線程的四種方法:
1. 通過全局函數創建線程
線程類的構造函數是變參構造函數,第一個參數是線程函數,后面的參數為線程函數的參數(參數通過值傳遞方式,若要引用傳遞須加std::ref())。
thread t1 (counter, 1, 6); //void counter(int, int);
2. 通過函數對象創建線程
//class Counter 實現 operator()
1) thread t1{Counter(1, 20)}; //c++統一推薦方法
2) Counter c(1, 20);
thread t2(c);
3) thread t3(Counter(1,20));
比較第一種和第三種構造方式,如果函數對象的構造函數不需要任何參數。 thread t3(Counter());是不行的,因為編譯器會認為你在聲明一個函數,函數名為t3,此時只能用第一種構造方式。
3. 通過lambda表達式創建線程
thread t1 ([](int, int){/*函數體*/}, 1, 6);
4. 通過成員函數創建線程
// class Counter c();
thread t{&Counter::process, &c};
一般常見的是一個類自己創建一個后台處理線程:thread t{&Counter::process, this};
線程本地存儲 thread_local
thread_local int n;
n作為線程參數傳遞給線程,那么每個線程有一個n的副本,在線程整個生命周期中存在,且只初始化一次,如同static局部變量。
二、原子操作庫<atomic>
多線程編程經常需要操作共享的內存,在讀/寫過程中會導致競爭條件。
例如:
int counter = 0;
............
++counter; //因為++counter不時原子操作,多個線程中出現此操作時不是線程安全的。
應該用:
atomic<int> counter(0); //等效於 atomic_int counter(0);
............
++counter; //此時多個線程執行++counter是一個原子操作,是線程安全的。
例: void func( std::atomic<int>& counter) { for( int i=0; i<1000; ++i ) ++counter; } int main() { std::atomic<int> counter(0); std::vector<std::thread> threads; for( int i=0; i<10; ++i ) //線程參數總是值傳遞,若要傳遞引用,須加std::ref()。(頭文件<functional>中) threads.push_back( std::thread{ func, std::ref(counter)} ); for( auto& t : threads ) t.join(); //調用join,如果線程未結束,則main函數阻塞於此。 std::count<<"Result="<<counter<<std::endl; return 0; } /*join的調用會導致調用線程阻塞於此,若不希望調用線程阻塞,但又想知道被調線程是否結束,應當用其它方式,例如消息...*/
三、互斥 <mutex>
編寫多線程必須分外留意操作順序,如果無法避免線程共享數據,則必須提供同步機制,保證一次只有一個線程能更改數據。使用互斥解決競爭條件,可能導致死鎖。
1. 互斥體類
1) 非定時互斥體類 std::mutex std::recursive_mutex
lock() : 嘗試獲取鎖,並且阻塞直到獲取鎖。
try_lock() : 嘗試獲取鎖,並立即返回,成功獲取返回true,否則false。
unlock() : 釋放鎖。
mutex與recursive_mutex的區別在於,前者已經獲得所后不得再嘗試獲取,這會死鎖,后者能遞歸獲取,注意釋放次數應與獲取次數相等。
2) 定時互斥鎖類 std::timed_mutex std::recursive_timed_mutex
lock() , try_lock() , unlock()
try_lock_for(rel_time) : 指定相對時間內獲得返回true, 超時返回false。
try_lock_until(abs_time) : 指定系統絕對時間內獲得返回true, 超時返回false。
timed_mutex與recursive_timed_mutex區別同上。
2. 鎖類
鎖類是一個包裝器,析構函數會自動釋放關聯的互斥體。
1) 簡單鎖 std::lock_guard
其構造函數會要求獲得互斥體,並阻塞直到獲得鎖。
2) 復雜鎖 std::unique_lock
explict unique_lock( mutex_type& m); //阻塞直到獲得鎖。 unique_lock(mutex_type& m, defer_lock_t) noexcept; //保存一個互斥體引用,不會立即嘗試獲得鎖。鎖可以在以后獲得。 unique_lock(mutex_type& m, try_to_lock_t); //嘗試獲得引用的互斥鎖,未能獲得也不阻塞。 unique_lock(mutex_type& m, adopt_lock_t); //該鎖假定線程獲得引用的互斥鎖,並負責管理這個鎖。 template<class Clock, class Duration> unique_lock(mutex& m, const chrono::time_point<Clock, Duration>& abs_time); //嘗試獲取該鎖,直到超過給定的絕對時間。 template<class Rep, class Period> unique_lock(mutex& m, const chrono::duration<Rep, Period>& rel_time); //嘗試獲取該鎖,直到超過給定的相對時間。
unique_lock類還支持lock(), try_lock(), try_lock_for(), try_lock_until()等方法。
通過owns_lock()查看是否獲得了這個鎖;也可以用if對unique_lock對象直接判斷是否獲得鎖,因為它定義了bool()運算符。
3. 獲得多個互斥體對象上的鎖
1) 泛型lock可變參數模板函數
template <class L1, class L2, class...L3>
void lock(L1&, L2&, L3&...);
按順序鎖定,如果一個互斥體拋出異常,會對已獲得的鎖unlock。
2) 泛型try_lock
template <class L1, class L2, class...L3>
int try_lock(L1&, L2&, L3&...);
通過順序調用互斥體對象的try_lock,成功返回-1,失敗返回從0開始的位置索引,並對已獲得的鎖unlock。
參數順序每次應保持一致, 否則易死鎖。
4. std::call_once std::once_flag
保證call_once調度的函數只被執行一次。
5. 實例:
// 1. 簡單鎖 mutex mMutex; lock_guard<mutex> mLock(mMutex); // 2. 定時鎖 timed_mutex mTimeMutex; unique_lock<timed_mutex> mLock(mTimedMutex, chrono::milliseconds(200)); // 3. 泛型 mutex mut1; mutex mut2; unique_lock<mutex> lock1(mut1, defer_lock_t()); unique_lock<mutex> lock2(mut2, defer_lock_t()); lock(lock1, lock2); // 4. 雙重檢查鎖定算法 (代替call_once的用法) class MyClass { public: void init() { p = new int(0); cout<<"Init"<<endl;} private: int *p; } MyClass var; bool initialized = false; mutex mut; void func() { if( ! initialized) //一次檢查 { unique_lock<mutex> lock1(mut); if( ! initialized) //兩次檢查 { var.init(); initialized = true; } } cout<<"OK"<<endl; } //兩次檢查initialized。獲得鎖之前和獲得鎖之后,確保init只調用一次。
四、條件變量 <condition_variable>
1. std::condition_variable 只能等待unique_lock<mutex>的條件變量
notify_one(); //喚醒等待這個條件變量的線程之一 notify_all(); //喚醒所有等待這個條件變量的線程 // 1)前提是已經獲得lk的鎖 // 2)調用wait會unlock lk,然后等待 // 3)當被喚醒后 lock lk wait( unique_lock<mutex>& lk); wait_for(unique_lock<mutex>& lk, const chrono::duration<Rep,Period>& rel_time); wait_until(unique_lock<mutex>&lk, const chrono::time_point<Clock,Duration>& abs_time);
2. std::condition_variable_any 支持任何類型的Lock類
3.
//例:向隊列中加入數據,當隊列不為空時,后台線程被喚醒處理數據 std::queue<std::string> mQueue; std::mutex mMutex; std::condition_variable mCondVar; //向隊列加入數據的線程 unique_lock<mutex> lock(mMutex); mQueue.push( data); mCondVar.notify_all(); //后台處理數據的線程 unique_lock<mutex> lock(mMutex); while(true) { //1.先釋放lock 2.然后等待被喚醒 3.被喚醒后等待獲取lock mCondVar.wait(lock); // process... }
五、 future
promise/future模型方便獲取線程返回的結果、線程間通信、處理異常