1. 程式人生 > 程式設計 >c++11新特性多執行緒操作實戰

c++11新特性多執行緒操作實戰

c++11多執行緒操作

執行緒

thread

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的拷貝構造和賦值運算子過載的原型是:

thread(const thread&) = delete;
thread& operator=(const thread&) = delete;

被禁用了,但是t5,t6執行緒是建立成功的。std::move把t1轉換為右值,呼叫的是函式原型為thread& operator=(thread&& _Other) noexceptthread(thread&& _Other) noexcept

當執行緒物件t1被移動拷貝和移動賦值給t5和t6的時候,t1就失去了執行緒控制權,也就是一個執行緒只能同時被一個執行緒物件所控制。最直觀的是t1.joinable()返回值為false,joinable()函式後面介紹。

使用類成員函式作為執行緒引數:

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 無效果)。

#include <iostream>
#include <chrono>
#include <thread>
 
// 建議其他執行緒執行一小段時間的“忙睡眠”
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<std::chrono::microseconds>(elapsed).count()
       << " microseconds\n";
}

get_id

這個函式不用過多介紹了,就是用來獲取當前執行緒id的,用來標識執行緒的身份。

std::thread::id this_id = std::this_thread::get_id();

sleep_for

位於this_thread名稱空間下,msvc下支援兩種時間引數。

std::this_thread::sleep_for(2s);
std::this_thread::sleep_for(std::chrono::seconds(1));

sleep_untile

引數構建起來挺麻煩的,一般場景下要求執行緒睡眠的就用sleep_for就行了

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),那麼,這個許可證就是互斥量。互斥量保證了使用印表機這一過程不被打斷。

程式碼示例:

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
提供互斥設施,實現有時限鎖定

recursive_mutex
提供能被同一執行緒遞迴鎖定的互斥設施

recursive_timed_mutex
提供能被同一執行緒遞迴鎖定的互斥設施,並實現有時限鎖定

通用互斥管理

lock_guard

void Test1()
{
  std::lock_guard<std::mutex> 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

到此這篇關於c++11新特性多執行緒操作實戰的文章就介紹到這了,更多相關c++11 多執行緒操作內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!