void func(int i, double d, const string& s) { cout << i << ", " << d << ", " << s << endl; } int main() { thread t(func, 1, 12.50, "sample"); t.join(); system("pause"); return 0; }
上例中,t 是一個線程對象,函數func()運行於該線程中。對join()函數的調用將使調用線程(本例是指主線程)一直處於阻塞狀態,直到正在執行的線程t執行結束。如果線程函數返回某個值,該值也將被忽略。該函數可以接收任意數量的參數。
盡管可以向線程函數傳遞任意數量的參數(指的是func形參的個數可以任意個數),但是所有的參數應當按值傳遞。如果需要將參數按引用傳遞,必須將參數用std::ref 或者std::cref進行封裝。
void func(int& a) { a++; } int main() { int a = 42; thread t(func, ref(a)); t.join(); cout << a << endl; system("pause"); return 0; } //輸出43,如果不用ref,運行出錯,why?
//在thread的構造函數中,線程函數的參數被拷貝(淺拷貝)到線程獨立內存中,這樣可以被線程對象訪問,即使函數形參是引用 //線程構造函數拷貝data,傳遞給函數的參數是data拷貝的引用,而非數據本身的引用,若用ref封裝data,則update函數就會 //接收到data變量的引用,而非data拷貝的引用 struct widget { int a; string s; widget(int b,string ss):a(b),s(ss){} }; void update(widget &data) { data.a += 1; data.s += "233"; } int main() { widget data(2, "ljy"); thread t(update, data); t.join(); cout << data.a << " " << data.s << endl;//2 ljy system("pause"); return 0; }
thread的一些常用函數:位於std::this_thread命名空間中
- get_id: 返回當前線程的id.
- yield:在處於等待狀態時,可以讓調度器先運行其他可用的線程。
- sleep_for:阻塞當前線程,時間不少於其參數指定的時間。
- sleep_util:在參數指定的時間到達之前,使當前線程一直處於阻塞狀態。
鎖:
mutex: 提供了核心函數 lock() 和 unlock(),以及非阻塞方法的try_lock()方法,一旦互斥量不可用,該方法會立即返回。
recursive_mutex:允許在同一個線程中對一個互斥量的多次請求
mutex g_lock;
void func() { //對互斥量加鎖,如果互斥量不可用,便處於阻塞狀態 g_lock.lock(); cout << "entered thread" << this_thread::get_id() << endl; this_thread::sleep_for(chrono::seconds(rand() % 10)); cout << "leaving thread" << this_thread::get_id() << endl; //對互斥量解鎖 g_lock.unlock(); } int main() { //與rand配合使用,實現真正的隨機 srand((unsigned int)time(0)); thread t1(func); thread t2(func); thread t3(func); t1.join(); t2.join(); t3.join(); system("pause"); return 0; }
recursive_mutex允許同一個線程多次獲取同一個互斥量。
//實現一個線程安全容器 template<class T> class container { private: recursive_mutex _lock; vector<T> _elements; public: void add(T element) { _lock.lock(); _elements.push_back(element); _lock.unlock(); } void addrange(int index, vector<T>vec) { for (int i = 0; i < index; ++i) { _lock.lock(); add(vec[i]); _lock.unlock(); } } void dump() { _lock.lock(); for (auto e : _elements) cout << e << endl; _lock.unlock(); } }; void func(container<int>& cont) { vector<int>vec = { 1,2,3,4,5,6,7 }; cont.addrange(3, vec); } int main() { container<int> cont; thread t1(func,ref(cont)); thread t2(func,ref(cont)); thread t3(func,ref(cont)); t1.join(); t2.join(); t3.join(); cont.dump(); system("pause"); return 0; }
顯式的加鎖和解鎖會導致一些問題,比如忘記解鎖或者請求加鎖的順序不正確,進而產生死鎖。該標准提供了一些類和函數幫助解決此類問題。這些封裝類保證了在RAII風格上互斥量使用的一致性,可以在給定的代碼范圍內自動加鎖和解鎖。封裝類包括:
lock_guard:在構造對象時,它試圖去獲取互斥量的所有權(通過調用lock()),在析構對象時,自動釋放互斥量(通過調用unlock()).這是一個不可復制的類。
unique_lock:這個一通用的互斥量封裝類,不同於lock_guard,它還支持延遲加鎖,時間加鎖和遞歸加鎖以及鎖所有權的轉移和條件變量的使用。這也是一個不可復制的類,但它是可移動類。
(unique_lock及lock_guard的具體區別、實現要弄明白)
1 采用RAII手法(對象管理資源)管理mutex的std::lock_guard其功能是在對象構造時將mutex加鎖,析構時對mutex解鎖,這樣一個棧對象保證了在異常情形下mutex可以在lock_guard對象析構被解鎖,lock_guard擁有mutex的所有權(mutex已被lock)。
explicit lock_guard (mutex_type& m);//必須要傳遞一個mutex作為構造參數,在構造函數中對mutex上鎖 lock_guard (mutex_type& m, adopt_lock_t tag);//tag=adopt_lock表示mutex已經在之前被上鎖,這里lock_guard將擁有mutex的所有權 lock_guard (const lock_guard&) = delete;//不允許copy constructor
2 再來看一個與std::lock_guard功能相似但功能更加靈活的管理mutex的對象 std::unique_lock,unique_lock內部持有mutex的狀態:locked,unlocked。unique_lock比lock_guard占用空間和速度慢一些,因為其要維護mutex的狀態。
1 unique_lock() noexcept; //可以構造一個空的unique_lock對象,此時並不擁有任何mutex 2 explicit unique_lock (mutex_type& m);//擁有mutex,並調用mutex.lock()對其上鎖 3 unique_lock (mutex_type& m, try_to_lock_t tag);//tag=try_lock表示調用mutex.try_lock()嘗試加鎖 4 unique_lock (mutex_type& m, defer_lock_t tag) noexcept;//tag=defer_lock表示不對mutex加鎖,只管理mutex,此時mutex應該是沒有加鎖的 5 unique_lock (mutex_type& m, adopt_lock_t tag);//tag=adopt_lock表示mutex在此之前已經被上鎖,此時unique_locl管理mutex 6 template <class Rep, class Period> unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time);//在一段時間rel_time內嘗試對mutex加鎖,mutex.try_lock_for(rel_time) 7 template <class Clock, class Duration> unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);//mutex.try_lock_until(abs_time)直到abs_time嘗試加鎖 8 unique_lock (const unique_lock&) = delete;//禁止拷貝構造 9 unique_lock (unique_lock&& x);//獲得x管理的mutex,此后x不再和mutex相關,x此后相當於一個默認構造的unique_lock,移動構造函數,具備移動語義,movable but not copyable
說明:其中2和5擁有mutex的所有權(mutex被lock),而1和4不擁有mutex的所有權,3和6及7若嘗試加鎖成功則擁有mutex的所有權
unique_lock 在使用上比lock_guard更具有彈性,和 lock_guard 相比,unique_lock 主要的特色在於:
- unique_lock 不一定要擁有 mutex,所以可以通過 default constructor 建立出一個空的 unique_lock。
- unique_lock 雖然一樣不可復制(non-copyable),但是它是可以轉移的(movable)。所以,unique_lock 不但可以被函數回傳,也可以放到 STL 的 container 里。
- 另外,unique_lock 也有提供 lock()、unlock() 等函數,可以用來加鎖解鎖mutex,也算是功能比較完整的地方。
- unique_lock本身還可以用於std::lock參數,因為其具備lock、unlock、try_lock成員函數,這些函數不僅完成針對mutex的操作還要更新mutex的狀態。
3 std::unique_lock其它成員函數
~unique_lock();//若unique_lock對象擁有管理的mutex的所有權,mutex沒有被銷毀或者unlock,那么將執行mutex::unlock()解鎖,並不銷毀mutex對象。 mutex_type* mutex() const noexcept;//返回unique_lock管理的mutex指針,但是unique_lock不會放棄對mutex的管理,若unique_lock對mutex上鎖了,其有義務對mutex解鎖 bool owns_lock() const noexcept;//當mutex被unique_lock上鎖,且mutex沒有解鎖或析構,返回真,否則返回false explicit bool operator () const noexcept;//同上
4 std::unique_lock增加了靈活性,比如可以對mutex的管理從一個scope通過move語義轉到另一個scope,不像lock_guard只能在一個scope中生存。同時也增加了管理的難度,因此如無必要還是用lock_guard。
5 網上看見一個unique_lock的應用於銀行轉賬的實例,貼在這里:
struct bank_account//銀行賬戶 { explicit bank_account(string name, int money) { sName = name; iMoney = money; } string sName; int iMoney; mutex mMutex;//賬戶都有一個鎖mutex }; void transfer(bank_account &from, bank_account &to, int amount)//這里缺少一個from==to的條件判斷個人覺得 { unique_lock<mutex> lock1(from.mMutex, defer_lock);//defer_lock表示延遲加鎖,此處只管理mutex unique_lock<mutex> lock2(to.mMutex, defer_lock); lock(lock1, lock2);//lock一次性鎖住多個mutex防止deadlock,這個是關鍵 from.iMoney -= amount; to.iMoney += amount; cout << "Transfer " << amount << " from " << from.sName << " to " << to.sName << endl; } void main() { bank_account Account1("User1", 100); bank_account Account2("User2", 50); thread t1([&]() { transfer(Account1, Account2, 10); });//lambda表達式,注意此處Account1,Account2都是傳入引用,值會發生改變 thread t2([&]() { transfer(Account2, Account1, 5); }); t1.join(); t2.join(); system("pause"); }
采用lock_guard也可以如下:
lock( from.mMutex, to.mMutex ); lock_guard<mutex> lock1( from.mMutex, adopt_lock );//adopt_lock表示mutex已經上鎖,lock1將擁有from.mMutex lock_guard<mutex> lock2( to.mMutex, adopt_lock );
條件變量:它能使一個或多個線程進入阻塞狀態(線程調用wait方法),直到接到另一個線程的通知,或者發生超時或虛假喚醒時,才退出阻塞
condition_variable:要求任何在等待該條件變量的線程必須先獲取std::unique_lock鎖
條件變量的工作原理:
至少有一個線程在等待某個條件(該條件與條件變量無關)變為true,等待的線程必須先獲取unique_lock 鎖。該鎖被傳遞給wait()方法,wait()方法會釋放互斥量,並將線程掛起,直到條件變量接收到信號。收到信號后,線程會被喚醒,同時該鎖也會被重新獲取
mutex m;
condition_variable cond;
int flag = 0; void producer() { this_thread::sleep_for(chrono::seconds(1)); lock_guard<mutex> guard(m); flag = 100; cond.notify_one(); cout << "notify..." << endl; } void customer() { unique_lock<mutex> lk(m); if (m.try_lock()) cout << "mutex unlocked after unique_lock" << endl; else cout << "mutex locked after unique_lock" << endl;//輸出 while (flag == 0) { cout << "wait..." << endl; cond.wait(lk); } if (m.try_lock()) cout << "mutex unlocked after wait" << endl; else cout << "mutex locked after wait" << endl;//輸出 cout << "flag==100? " << flag << endl; } /* mutex locked after unique_lock wait... notify... mutex locked after wait flag==100? 100 */ int main() { thread one(producer); thread two(customer); one.join(); two.join(); system("pause"); return 0; }
可以使用notify_one()來發送信號,喚醒一個正在等待該條件收到信號的處於阻塞狀態的線程,或者用notify_all()來喚醒在等待該條件的所有線程。
在多處理器系統中,因為一些復雜情況,要想完全預測到條件被喚醒並不容易,還會出現虛假喚醒的情況。就是說,在沒人給條件變量發送信號的情況下,線程也可能會被喚醒。所以線程被喚醒后,還需要檢測條件是否為true(在while循環中調用wait)。因為可能會多次發生虛假喚醒,所以需要進行循環檢測。
wait方法帶有鎖unique_lock,這個方法可以釋放鎖,阻塞線程,並把線程添加到正在等待這一條件變量的線程隊列里面。當該條件變量收到信號或者發生虛假喚醒時,線程就會被喚醒。它們其中任何一個發生時,鎖都會被重新獲取
條件變量是非常底層的同步原語,很少直接使用,一般都是用它來實現高層的同步措施,如BlockingQueue或CountDownLatch
阻塞隊列實現:當隊列為空時,從隊列獲取元素的操作將會被阻塞,直到隊列中被放入了元素;當隊列滿時,往隊列里存放元素的操作也會被阻塞,直到有元素被從隊列中取出(以上的操作都是基於不同的線程來說的,線程在對阻塞隊列進程操作時會被阻塞)。
template<typename T>
class BlockingQueue { public: BlockingQueue(){}
//lock_guard保證對隊列的互斥訪問,condition_variable喚醒阻塞線程,實現線程同步,Put操作應該也要有一個condition_variable
//我的理解:put也用unique_lock,在while中判斷隊列是否滿了,若滿了,調用condition_variable.wait阻塞自己 void Put(const T& task) { //臨界區 { std::lock_guard<std::mutex> lock(_mutex); _queue.push_back(task); } _condvar.notify_all(); } T Take() { std::unique_lock<std::mutex> lock(_mutex);
//此處應該在while循環中調用,防止虛假喚醒 _condvar.wait(lock, [this] {return !_queue.empty(); }); assert(!_queue.empty()); T front(_queue.front()); _queue.pop_front(); return front; } size_t Size() const { std::lock_guard<std::mutex> lock(_mutex); return _queue.size(); } private: BlockingQueue(const BlockingQueue& rhs); BlockingQueue& operator = (const BlockingQueue& rhs); private: mutable std::mutex _mutex; std::condition_variable _condvar; std::list<T> _queue;//雙向鏈表 }; int main() { BlockingQueue<int> q; auto t1 = std::async(std::launch::async, [&q]() { for (int i = 0; i < 10; ++i) { q.Put(i); } }); auto t2 = std::async(std::launch::async, [&q]() { while (q.Size()) { std::cout <<"t2 "<< q.Take() << std::endl; } }); auto t3 = std::async(std::launch::async, [&q]() { while (q.Size()) { std::cout <<"t3 "<< q.Take() << std::endl; } }); t1.wait(); t2.wait(); t3.wait(); system("pause"); return 0; }
轉自:http://blog.csdn.net/cywosp/article/details/9157379
CountDownLatch:
用C++11的std::async代替線程的創建:
線程是屬於比較低層次的東西,有時候使用有些不便,比如我希望獲取線程函數的返回結果的時候,我就不能直接通過thread.join()得到結果,這時就必須定義一個變量,在線程函數中去給這個變量賦值,然后join,最后得到結果,這個過程是比較繁瑣的。
c++11還提供了異步接口std::async,通過這個異步接口可以很方便的獲取線程函數的執行結果。std::async會自動創建一個線程去調用線程函數,它返回一個std::future,這個future中存儲了線程函數返回的結果,當我們需要線程函數的結果時,直接從future中獲取,非常方便。但是我想說的是,其實std::async給我們提供的便利可不僅僅是這一點,它首先解耦了線程的創建和執行,使得我們可以在需要的時候獲取異步操作的結果;其次它還提供了多個線程創建策略(比如可以通過延遲加載的方式去創建線程),使得我們可以以多種方式去創建線程。在介紹async具體用法以及為什么要用std::async代替線程的創建之前,我想先說一說std::future、std::promise和std::packaged_task。
std::future是一個非常有用也很有意思的東西,簡單說std::future提供了一種訪問異步操作結果的機制。從字面意思來理解,它表示未來,我覺得這個名字非常貼切,因為一個異步操作我們是不可能馬上就獲取操作結果的,只能在未來某個時候獲取,但是我們可以以同步等待的方式來獲取結果,可以通過查詢future的狀態(future_status)來獲取異步操作的結果。future_status有三種狀態:
- deferred:異步操作還沒開始
- ready:異步操作已經完成
- timeout:異步操作超時
獲取future結果有三種方式:get、wait、wait_for,其中get等待異步操作結束並返回結果,wait只是等待異步操作完成,沒有返回值,wait_for是超時等待返回結果。
std::promise為獲取線程函數中的某個值提供便利,在線程函數中給外面傳進來的promise賦值,當線程函數執行完成之后就可以通過promis獲取該值了,值得注意的是取值是間接通過promise內部提供的future來獲取的。它的基本用法:
std::promise<int> pr; std::thread t([](std::promise<int>& p) { p.set_value_at_thread_exit(9); }, std::ref(pr)); std::future<int> f = pr.get_future(); auto r = f.get(); cout << r << endl; t.join();
std::packaged_task它封裝了一個可調用的目標(如function, lambda expression, bind expression, or another function object),以便異步調用,它和promise在某種程度上有點像,promise保存了一個共享狀態的值,而packaged_task保存的是一個函數。它的基本用法:
std::packaged_task<int()> task([](){ return 7; }); std::thread t1(std::ref(task)); std::future<int> f1 = task.get_future(); auto r1 = f1.get();
至此, 我們介紹了std::async相關的幾個對象std::future、std::promise和std::packaged_task,其中std::promise和std::packaged_task的結果最終都是通過其內部的future返回出來的,不知道讀者有沒有搞糊塗,為什么有這么多東西出來,他們之間的關系到底是怎樣的?且聽我慢慢道來,std::future提供了一個訪問異步操作結果的機制,它和線程是一個級別的屬於低層次的對象,在它之上高一層的是std::packaged_task和std::promise,他們內部都有future以便訪問異步操作結果,std::packaged_task包裝的是一個異步操作,而std::promise包裝的是一個值,都是為了方便異步操作的,因為有時我需要獲取線程函數中的某個值,這時就用std::promise,而有時我需要獲取一個異步操作的返回值,這時就用std::packaged_task(我的理解:packaged_task就是這個異步操作)。那std::promise和std::packaged_task之間又是什么關系呢?說他們沒關系也關系,說他們有關系也有關系,都取決於你了,因為我可以將一個異步操作的結果保存到std::promise中。
std::async先將異步操作用std::packaged_task包裝起來(我的理解是將線程函數包裝在packaged_task中),然后將異步操作的結果放到std::promise中,這個過程就是創造未來的過程。外面再通過future.get/wait來獲取這個未來的結果,怎么樣,std::async真的是來幫忙的吧,你不用再想到底該怎么用std::future、std::promise和std::packaged_task了,std::async已經幫你搞定一切了!
現在來看看std::async的原型async(std::launch::async | std::launch::deferred, f, args...),第一個參數是線程的創建策略,有兩種策略,默認的策略是立即創建線程:
- std::launch::async:在調用async就開始創建線程。
- std::launch::deferred:延遲加載方式創建線程。調用async時不創建線程,直到調用了future的get或者wait時才創建線程。
第二個參數是線程函數,第三個參數是線程函數的參數。
int main() { std::future<int> f1 = std::async(std::launch::async, []() { return 8; }); cout << f1.get() << endl; //output: 8 std::future<void> f2 = std::async(std::launch::async, []() { cout << 8 << endl; }); f2.wait(); //output: 8 std::future<int> future = std::async(std::launch::async, []() { std::this_thread::sleep_for(std::chrono::seconds(3)); return 8; }); std::cout << "waiting...\n"; std::future_status status; do { status = future.wait_for(std::chrono::seconds(1)); if (status == std::future_status::deferred) { std::cout << "deferred\n"; } else if (status == std::future_status::timeout) { std::cout << "timeout\n"; } else if (status == std::future_status::ready) { std::cout << "ready!\n"; } } while (status != std::future_status::ready); std::cout << "result is " << future.get() << '\n'; system("pause"); return 0; }
總結:
std::async是更高層次上的異步操作,使我們不用關注線程創建內部細節,就能方便的獲取異步執行狀態和結果,還可以指定線程創建策略,應該用std::async替代線程的創建,讓它成為我們做異步操作的首選。
轉自:http://www.cnblogs.com/qicosmos/p/3534211.html
lambda基本語法:
簡單來說,Lambda函數也就是一個函數,它的語法定義如下:
[capture](parameters) mutable ->return-type{statement}
- [capture]:捕捉列表。捕捉列表總是出現在Lambda函數的開始處。實際上,[]是Lambda引出符。編譯器根據該引出符判斷接下來的代碼是否是Lambda函數。捕捉列表能夠捕捉上下文中的變量以供Lambda函數使用;
- (parameters):參數列表。與普通函數的參數列表一致。如果不需要參數傳遞,則可以連同括號“()”一起省略;
- mutable:mutable修飾符。默認情況下,Lambda函數總是一個const函數,mutable可以取消其常量性。在使用該修飾符時,參數列表不可省略(即使參數為空);
- ->return-type:返回類型。用追蹤返回類型形式聲明函數的返回類型。我們可以在不需要返回值的時候也可以連同符號”->”一起省略。此外,在返回類型明確的情況下,也可以省略該部分,讓編譯器對返回類型進行推導;
- {statement}:函數體。內容與普通函數一樣,不過除了可以使用參數之外,還可以使用所有捕獲的變量。
與普通函數最大的區別是,除了可以使用參數以外,Lambda函數還可以通過捕獲列表訪問一些上下文中的數據。具體地,捕捉列表描述了上下文中哪些數據可以被Lambda使用,以及使用方式(以值傳遞的方式或引用傳遞的方式)。語法上,在“[]”包括起來的是捕捉列表,捕捉列表由多個捕捉項組成,並以逗號分隔。捕捉列表有以下幾種形式:
- [var]表示值傳遞方式捕捉變量var;
- [=]表示值傳遞方式捕捉所有父作用域的變量(包括this);
- [&var]表示引用傳遞捕捉變量var;
- [&]表示引用傳遞方式捕捉所有父作用域的變量(包括this);
- [this]表示值傳遞方式捕捉當前的this指針。
上面提到了一個父作用域,也就是包含Lambda函數的語句塊,說通俗點就是包含Lambda的“{}”代碼塊。上面的捕捉列表還可以進行組合,例如:
- [=,&a,&b]表示以引用傳遞的方式捕捉變量a和b,以值傳遞方式捕捉其它所有變量;
- [&,a,this]表示以值傳遞的方式捕捉變量a和this,引用傳遞方式捕捉其它所有變量。
不過值得注意的是,捕捉列表不允許變量重復傳遞。下面一些例子就是典型的重復,會導致編譯時期的錯誤。例如:
- [=,a]這里已經以值傳遞方式捕捉了所有變量,但是重復捕捉a了,會報錯的;
- [&,&this]這里&已經以引用傳遞方式捕捉了所有變量,再捕捉this也是一種重復。
int a = 1; int b = 2; auto func = [=, &b](int c)->int {return b += a + c; }; cout<<func(a);
讀寫鎖:http://blog.csdn.net/inszva/article/details/51571315
虛假喚醒:
即使沒有線程調用condition_signal, 原先調用condition_wait的函數也可能會返回。此時線程被喚醒了,但是條件並不滿足,這個時候如果不對條件進行檢查而往下執行,就可能會導致后續的處理出現錯誤。
解決措施:把判斷bool條件和wait()放到while循環中
http://blog.jobbole.com/44409/
http://blog.csdn.net/column/details/ccia.html
http://www.cnblogs.com/haippy/p/3346477.html