C++11 增加了對多線程的支持,是多線程編程變得簡單、易用。
一、線程的創建
#include <string>
#include <thread>
//用於時間延時 獲取時間
#include <chrono>
#include <iostream>
using namespace std;
void test_create(string str){
for(int i=0; i<10; i++){
//讓本線程休眠100毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cout << "sleep for : 100ms, string:" << str << endl;
}
}
void createThread(){
//第一個參數是t1線程要執行的函數,第二個參數是test_create函數的參數
thread t1(test_create, "hello");
//阻塞當前線程,直到t1執行完成,
//如果不用join也可以使用detach,不過用了detach之後,該線程就變成了孤兒線程
t1.join();
cout << " t1 is end" << endl;
}
二、互斥量的使用
當多個線程共同訪問一個臨界區的時候,如果沒有任何處理會造成數據安全問題。我們可以使用互斥量來解決此問題。
頭文件mutex
#include <mutex>
int num = 0;
mutex t_lock;
void test_mutex_1(){
for(int i=0; i<100; i++){
//此方式需要自行加鎖解鎖
t_lock.lock();
num++;
//臨界區訪問結束後,需要解鎖
t_lock.unlock();
}
}
void test_mutex_2(){
for(int i=0; i<100; i++){
t_lock.lock();
num++;
t_lock.unlock();
}
}
void test_mutex(){
cout << "before num:" << num << endl;
//多線程共同訪問一塊臨界區,需要用到互斥量
thread t1(test_mutex_1);
thread t2(test_mutex_2);
t1.join();
t2.join();
cout << "after num:" << num << endl;
}
C++11還提供了lock_guard和unique_guard來處理鎖的問題。
lock_guard的使用:
lock_guard使用了RAII機制可以確保安全釋放mutex。在構造lock_guard對象的時候,需要傳入mutex對象,此時mutex會被當前線程鎖住,直到lock_guard對象被析構的時候,纔會釋放mutex。不需要我們自己做加鎖和解鎖的操作。
lock_guard不負責mutex生命週期的管理,它只是簡化了mutex的加鎖、解鎖的操作。lock_guard生命週期內,mutex處於加鎖狀態,lock_guard生命週期結束後,它所管理的鎖會被釋放。
void test_lock_guard_1(){
lock_guard<mutex> lg(t_lock);
for(int i=0; i<100; i++){
num++;
}
}
void test_lock_guard_2(){
lock_guard<mutex> lg(t_lock);
for(int i=0; i<100; i++){
num++;
}
}
void test_lock_guard(){
cout << "before num:" << num << endl;
thread t1(test_lock_guard_1);
thread t2(test_lock_guard_2);
t1.join();
t2.join();
cout << "after num:" << num << endl;
}
unique_lock的使用:
除了提供與lock_guard相同的功能之外,還提供了更多的函數,相較於lock_guard來說,更加的靈活一些。通過使用unique_lock的函數可以靈活的控制鎖的範圍,減小鎖的粒度,並且還可以控制鎖的時間等。
unique_lock內部持有mutex的狀態:lock和unlock。所以unique_lock比lcok_guard更加的佔用空間,並且速度會更慢一些,因爲它要維護mutex的狀態。
lock | locks the associated mutex (public member function) |
try_lock | tries to lock the associated mutex, returns if the mutex is not available (public member function) |
try_lock_for | attempts to lock the associated TimedLockable mutex, returns if the mutex has been unavailable for the specified time duration (public member function) |
try_lock_until | tries to lock the associated TimedLockable mutex, returns if the mutex has been unavailable until specified time point has been reached (public member function) |
unlock |
unlocks the associated mutex |
unique_guard() | 默認構造函數,此時不用任何的mutex |
unique_lock(mutex_type &m) | 獲取mutex,並調用mutex.lock()對其加鎖 |
unique_lock(mutex_type &m, try_to_lock_t_tag) | tag=try_lock表示調用mutex.try_lock()嘗試加鎖 |
unique_lock(mutex_type &m, defer_lock_t_tag) | tag=defer_lock,表示不加鎖,只是管理mutex,此時mutex是不加鎖狀態 |
unique_lock(mutex_type &m, adopt_lock_t_tag) | tag=adopt_lock,表示在此之前mutex已經加鎖,此時unique_guard管理mutex |
unique_lock(mutex_type &m, const chrono::duration<Rep, Period>& rel_time) |
在一段時間內嘗試對mutex加鎖,mutex.try_lock_for(rel_time) |
unique_lock(mutex_type &m, const chrono::time_point<Clock, Duration>& abs_time) |
直到abs_time嘗試加鎖 |
unique_lock(unique_lock&& x) |
獲得x管理的mutex,此後mutex不再和x相關聯,此後x相當於一個默認構造的unique_lock。 |
條件變量
可以使用條件變量來實現多個線程之間的同步操作,當條件不滿足的時候,一直被阻塞,當滿足條件的時候,線程纔會被喚醒。
條件變量是利用線程間共享的全局變量進行同步的一種機制主要包含兩個動作:
- 一個線程因等待“條件變量的條件成立”而掛起
- 另外一個線程是“條件成立”,給出信號,從而喚醒被掛起的線程。
爲了防止競爭,條件變量總是和一個互斥鎖一起使用,通常情況下鎖是std::mutex,並且管理這個鎖只能是unique_lock
mutex con_mutex;
//線程共享的全局變量
condition_variable con_cv;
void show(int id)
{
std::unique_lock<std::mutex> lck(con_mutex);
con_cv.wait(lck);
std::cout << "id:" << id << std::endl;
}
void notify()
{
std::unique_lock<std::mutex> lck(con_mutex);
con_cv.notify_all(); //喚醒所有在此條件變量上的等待線程
}
void test_condition(){
std::thread t[8];
for (int i = 0; i < 8; i++)
{
t[i] = std::thread(show, i);
}
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "all thread lock......" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
notify();
for (auto & th : t) th.join();
}
三、原子類
類似於Java併發包中的Atomic類。C++11引入了原子操作的概念,原子操作更接近內核,編譯器保證這些操作都是原子性的,即任何時候都只有一個線程訪問這些資源,避免了鎖的使用,提高了效率。
atomic_int atomic_num(0);
void atomic_thread(){
for(int i=0; i<100; i++){
atomic_num++;
}
}
void test_atomic(){
cout << "befor, num:" << atomic_num << endl;
thread t1(atomic_thread);
thread t2(atomic_thread);
t1.join();
t2.join();
cout << "befor, num:" << atomic_num << endl;
}