c++11 新特性實戰 (一):多執行緒操作
阿新 • • 發佈:2020-09-27
# c++11 新特性實戰 (一)
## c++11多執行緒操作
* 執行緒
* **thread**
```c++
int main()
{
thread t1(Test1);
t1.join();
thread t2(Test2);
t2.join();
thread t3 = t1;
thread t4(t1);
thread t5 = std::move(t1);
thread t6(std::move(t1));
return 0;
}
```
t3,t4建立失敗,因為thread的拷貝構造和賦值運算子過載的原型是:
```c++
thread(const thread&) = delete;
thread& operator=(const thread&) = delete;
```
被禁用了,但是t5, t6執行緒是建立成功的。std::move把t1轉換為右值,呼叫的是函式原型為`thread& operator=(thread&& _Other) noexcept`和`thread(thread&& _Other) noexcept`。
當執行緒物件t1被移動拷貝和移動賦值給t5和t6的時候,t1就失去了執行緒控制權,也就是一個執行緒只能同時被一個執行緒物件所控制。最直觀的是t1.joinable()返回值為false,joinable()函式後面介紹。
**使用類成員函式作為執行緒引數**:
```c++
class Task
{
public:
Task(){}
void Task1() {}
void Task2() {}
private:
};
int main()
{
Task task;
thread t3(&Task::Task1, &task);
t3.join();
return 0;
}
```
關鍵點是要建立一個類物件,並作為第二個引數傳入`thread()`執行緒的建構函式中去。
* **管理當前執行緒的函式**
* yield
此函式的準確性為依賴於實現,特別是使用中的 OS 排程器機制和系統狀態。例如,先進先出實時排程器( Linux 的 `SCHED_FIFO` )將懸掛當前執行緒並將它放到準備執行的同優先順序執行緒的佇列尾(而若無其他執行緒在同優先順序,則 `yield` 無效果)。
```c++
#include
#include
#include
// 建議其他執行緒執行一小段時間的“忙睡眠”
void little_sleep(std::chrono::microseconds us)
{
auto start = std::chrono::high_resolution_clock::now();
auto end = start + us;
do {
std::this_thread::yield();
} while (std::chrono::high_resolution_clock::now() < end);
}
int main()
{
auto start = std::chrono::high_resolution_clock::now();
little_sleep(std::chrono::microseconds(100));
auto elapsed = std::chrono::high_resolution_clock::now() - start;
std::cout << "waited for "
<< std::chrono::duration_cast(elapsed).count()
<< " microseconds\n";
}
```
* get_id
這個函式不用過多介紹了,就是用來獲取當前執行緒id的,用來標識執行緒的身份。
```c++
std::thread::id this_id = std::this_thread::get_id();
```
* sleep_for
位於this_thread名稱空間下,msvc下支援兩種時間引數。
```c++
std::this_thread::sleep_for(2s);
std::this_thread::sleep_for(std::chrono::seconds(1));
```
* sleep_untile
引數構建起來挺麻煩的,一般場景下要求執行緒睡眠的就用sleep_for就行了
```c++
using std::chrono::system_clock;
time_t tt = system_clock::to_time_t(system_clock::now());
struct std::tm *ptm = localtime(&tt);
std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));
```
* **互斥**
* mutex
對於互斥量看到一個很好的比喻:
> 單位上有一臺印表機(共享資料a),你要用印表機(執行緒1要操作資料a),同事老王也要用印表機(執行緒2也要操作資料a),但是印表機同一時間只能給一個人用,此時,規定不管是誰,在用印表機之前都要向領導申請許可證(lock),用完後再向領導歸還許可證(unlock),許可證總共只有一個,沒有許可證的人就等著在用印表機的同事用完後才能申請許可證(阻塞,執行緒1lock互斥量後其他執行緒就無法lock,只能等執行緒1unlock後,其他執行緒才能lock),那麼,這個許可證就是互斥量。互斥量保證了使用印表機這一過程不被打斷。
程式碼示例:
```c++
mutex mtx;
int gNum = 0;
void Test1()
{
mtx.lock();
for(int n = 0; n < 5; ++n)
gNum++;
mtx.unlock();
}
void Test2()
{
std::cout << "gNum = " << gNum << std::endl;
}
int main()
{
thread t1(Test1);
t1.join();
thread t2(Test2);
t2.join();
return 0;
}
```
join()表示主執行緒等待子執行緒結束再繼續執行,如果我們的期望是列印迴圈自增之後的gNum的值,那t1.join()就放在t2建立之前呼叫。因為**t2的建立就標誌著t2執行緒建立好然後開始執行了**。
通常mutex不單獨使用,因為lock和unlock必須配套使用,如果忘記unlock很可能造成死鎖,即使unlock寫了,但是如果在執行之前程式捕獲到異常,也還是一樣會死鎖。如何解決使用mutex造成的死鎖問題呢?下面介紹unique_gard和lock_guard的時候詳細說明。
* timed_mutex
```c++
std::mutex cout_mutex; // 控制到 std::cout 的訪問
std::timed_mutex mutex;
void job(int id)
{
using Ms = std::chrono::milliseconds;
std::ostringstream stream;
for (int i = 0; i < 3; ++i) {
if (mutex.try_lock_for(Ms(100))) {
stream << "success ";
std::this_thread::sleep_for(Ms(100));
mutex.unlock();
} else {
stream << "failed ";
}
std::this_thread::sleep_for(Ms(100));
}
std::lock_guard lock(cout_mutex);
std::cout << "[" << id << "] " << stream.str() << "\n";
}
int main()
{
std::vector threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(job, i);
}
for (auto& i: threads) {
i.join();
}
}
```
這裡的第28行衍生出一個知識點:**STL的`emplace_back`函式**。這是c++11新增的容器類的操作函式,如果第二個引數忽略,用法和`push_back`相似,都是在stl後面追加元素。函式原型:
```C++
template
decltype(auto) emplace_back(_Valty&&... _Val)
```
是一個變長的模板函式,例子中的程式碼傳遞的是一個**函式指標job**,`emplace_back`的實現會把job傳遞給`std::thread`的建構函式,與`push_back`需要是std::thread型別作為引數不同,所以`emplace_back`是直接在容器中構造了要新增的元素,省去了再次把引數拷貝到stl中的過程,效率更高。
提供互斥設施,實現有時限鎖定
* recursive_mutex
提供能被同一執行緒遞迴鎖定的互斥設施
* recursive_timed_mutex
提供能被同一執行緒遞迴鎖定的互斥設施,並實現有時限鎖定
* **通用互斥管理**
* lock_guard
```c++
void Test1()
{
std::lock_guard lg(mtx);
for(int n = 0; n < 5; ++n)
{
gNum++;
std::cout << "gNum = " << gNum << std::endl;
}
}
int main()
{
thread t1(Test1);
thread t2(Test1);
t1.join();
t2.join();
return 0;
}
```
lock_guard相當於利用RAII機制(“資源獲取就是初始化”)把mutex封裝了一下,在構造中lock,在析構中unlock。避免了中間過程出現異常導致的mutex不能夠正常unlock.
* scoped_lock(c++17)
* unique_lock
* defer_lock_t
* try_to_lock_t
* adopt_lock_t
* defer_lock
* try_to_lock
* adopt_lock
* **通用鎖演算法**
* try_lock
* lock
* **單次呼叫**
* once_flag
* call_once
* **條件變數**
* condition_variable
* condition_variable_any
* notify_all_at_thread_exit
* cv_status
* **Future**
* promise
* packaged_task
* future
* shared_future
* async
* launch
* future_status
* Future錯誤
* future_error
* future_category
* future_errc