c++11 線程:讓你的多線程任務更輕鬆

介紹

本文旨在幫助有經驗的Win32程序員來了解c++ 11線程庫及同步對象 和 Win32線程及同步對象之間的區別和相似之處。

在Win32中,所有的同步對象句柄(HANDLE)是全局句柄.它們可以被共享,甚至可以在進程間複製。在C++11中,所有的同步對象都是棧(stack)對象,這意味着它們必須是可“分離(detached)”的(如果支持“分離”的話)以便能夠被棧框架(stack frame)所析構。如果大量對象應該分離而你沒有,那麼它們便會無法實現自己的行動,而毀掉你的原本計劃。(譯者注:在pthread中,線程有joinable和unjoinable之分,具有joinable的線程在線程結束時,不會清空該線程所佔用的棧空間,通常的做法是在pthrea_create創建線程後,再調用pthread_join(有點waitforsingleobject的意思)纔會清空,而unjoinable的屬性的線程在線程結束時,就會自動清空所佔用空間)

所有的c++11同步對象都有一個native_handle()成員,它返回具體實現句柄(在win32,它就是一個handle)

在我的所有例子,我給出了win32僞代碼。祝你愉快!

小熊貓大暴走
小熊貓大暴走
翻譯於 3年前

3人頂

 翻譯的不錯哦!

背景知識

ox0000000.木有 :D。我也是c++11線程的新手。你需要自己去了解win32同步相關知識。這裏可能不是合適的同步技術的教程,而是一個C++11機制的快速引導,以便對你所指定的計劃有所幫助。

簡單成就完美

一個簡單例子:啓動一個線程,然後等它結束:

?
1
2
3
4
5
6
7
8
void foo()
  {
  }
void func()
  {
  std::thread t(foo); // Starts. Equal to CreateThread.
  t.join();  // Equal to WaitForSingleObject to the thread handle.
  }

與win32線程不同,你可以在這裏傳遞參數:

?
1
2
3
4
5
6
7
8
9
void foo(int x,int y)
  {
  // x = 4, y = 5.
  }
void func()
  {
  std::thread t(foo,4,5); // Acceptable.
  t.join(); 
  }

這樣,通過傳遞‘this’指針給std::thread讓成員函數成爲一個線程,變成了一件很簡單的事情.如果std::thread得以析構,而你沒有調用join(),它將會異常終止。脫離c++封裝運行線程:

?
1
2
3
4
5
6
7
8
9
void foo()
  {
  }
void func()
  {
      std::thread t(foo);
      // 在這裏已經調用了detach方法,c++對象從win32對象中脫離出來,如果此時還調用join方法,就會拋出std::system_error()
      t.detach();
  }

除了join(),detach()方法,還有joinable(),get_id(),sleep_for(),sleep_until().它們都是自解釋的,很好理解。

小熊貓大暴走
小熊貓大暴走
翻譯於 3年前

3人頂

 翻譯的不錯哦!

使用互斥(Mutex)

std::mutex與win32的臨界區(cirtical section)很類似。lock()如同EnterCriticalSectionunlock如同LeaveCriticalSectiontry_lock則像TryEnterCriticalSection

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::mutex m;
int j = 0;
void foo()
  {
  m.lock();        // 進入臨界區域
  j++;
  m.unlock();      // 離開
  }
void func()
  {
  std::thread t1(foo);
  std::thread t2(foo);
  t1.join();
  t2.join();
 // j = 2;
}
如上,你在lock一個 std::mutex 對象之後必須解鎖(unlock)。如果你已經對其加鎖,你不能再次lock。這與win32 不同,如果你已經在臨界區(critical section)裏,再次 EnterCriticalSection不會失敗,但是會增加一個計數。

嗨,不要走開哦。前面提到不能對std::mutex重複lock。這裏有std::recursive_mutex(誰發明的這名字),它的行爲則與臨界區(critical section)相似,可以重複lock。

?
1
2
3
4
5
6
7
8
9
std::recursive_mutex m;
void foo()
  {
  m.lock();
  m.lock(); // now valid
  j++;
  m.unlock();
  m.unlock(); // don't forget!
  }
此外,還有 std::timed_mutex, std::recursive_timed_mutex,他們提供 try_lock_fortry_lock_until方法,允許你等待一個lock,直到超時,或者達到定義的時間。
小熊貓大暴走
小熊貓大暴走
翻譯於 3年前

4人頂

 翻譯的不錯哦!

C++11 Thread的線程本地存儲(Thread Local Storage

TLS(thread local storage)類似,該功能允許你聲明一個帶有thread_local的聲明符的變量。這意味着,每一個線程都有自己的該全局變量的實例(instance),該實例的變量名就是全局變量名稱。

以前:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int j = 0;
void foo()
  {
  m.lock();
  j++;
  m.unlock();
  }
void func()
  {
  j = 0;
  std::thread t1(foo);
  std::thread t2(foo);
  t1.join();
  t2.join();
 // j = 2;
}
現在我們看:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
thread_local int j = 0;
void foo()
  {
  m.lock();
  j++; // j is now 1, no matter the thread. j is local to this thread.
  m.unlock();
  }
void func()
  {
  j = 0;
  std::thread t1(foo);
  std::thread t2(foo);
  t1.join();
  t2.join();
 // j still 0. The other "j"s were local to the threads
}
然而,Visual Studio還不支持 tls(我想這裏的tls應該是c++11 thread的tls)
小熊貓大暴走
小熊貓大暴走
翻譯於 3年前

2人頂

 翻譯的不錯哦!

神祕的變量

條件變量(Conditional variables)是能夠使線程等待特定條件的對象。在window系統裏,這些對象屬於用戶模式(usr-mode),因而不能被其他進程所共享。在window系統,條件變量與臨界區(critical section)有關,用來獲取或者釋放一個鎖。std::condition_variablestd::mutex聯用,也是這個原因。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::condition_variable c;
// 我們使用mutex而不是recursive_mutex是因爲該鎖需要一次性獲取和釋放
std::mutex mu; // We use a mutex rather than a recursive_mutex because the lock has to be acquired only and exactly once.
void foo5()
{
   std::unique_lock lock(mu); // Lock the mutex
   c.notify_one(); // WakeConditionVariable. It also releases the unique lock  等待條件變量,它也會釋放unque lock
}
void func5()
{
   std::unique_lock lock(mu); // Lock the mutex
   std::thread t1(foo5);
   // 等價與SleepConditionVariableCS,它解鎖mutex 變量nu,並允許foo5來加鎖
   c.wait(lock); // Equal to SleepConditionVariableCS. This unlocks the mutex mu and allows foo5 to lock it
   t1.join();
}

這並不像看上去那麼簡單。c.wait() 可能會返回,即使c.notify_one()沒有被調用(已知的這種情況是spurious wakeup - http://msdn.microsoft.com/en-us/library/windows/desktop/ms686301(v=vs.85).aspx)。通常,在Vista及以上操作系統,條件變量才被支持。

小熊貓大暴走
小熊貓大暴走
翻譯於 3年前

2人頂

 翻譯的不錯哦!

未來的承諾

設想這樣的情況,你希望一個線程做一些事情,然後返回你一個結果。同時,你在做一些其他的工作,該工作也許會也許不會花費你一點時間。你希望在某個特定的時間獲取那個線程的結果。

在win32中,你可以這樣

  • 用CreateThread啓動線程
  • 在線程裏,啓動任務,當準備完畢後發送一個事件(event),並把結果放在全局變量裏。
  • 在主函數裏(main)做其它的事情,然後在你想要結果的地方,調用WaitForSingleObject

在c++11,這個可以輕鬆被std::future實現,然後返回任何類型,因爲它是一個模板。

?
1
2
3
4
5
6
7
8
9
10
11
int GetMyAnswer()
   {
   return 10;
   }
int main()
  {
  std::future<int> GetAnAnswer = std::async(GetMyAnswer);  // GetMyAnswer starts background execution
  int answer = GetAnAnswer.get(); // answer = 10;
  // If GetMyAnswer has finished, this call returns immediately.
  // If not, it waits for the thread to finish.
  }
你也可以使用 std::promise。該對象可以提供一些 std::future以後需求的特性。如果在任何東西放入承諾(promise)之前你調用 std::future::get(),將會導致等待,直到承諾值(promised value)出現。如果std::promise::set_exception()被調用, std::future::get()則會拋出異常。如果 std::promise銷燬了,而你調用std::future::get(),你將會產生 broken_promise 異常。


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
std::promise<int> sex;
void foo()
  {
  // do stuff
  // 在下面的調用之後,future::get()將會返回該值
  sex.set_value(1); // After this call, future::get() will return this value.
     
  // 調用之後,future::get()將會拋出這個異常
  sex.set_exception(std::make_exception_ptr(std::runtime_error("TEST")));
  }
int main()
  {
  future<int> makesex = sex.get_future();
  std::thread t(foo);
   
  // do stuff
  try
    {
    makesex.get();
    hurray();
    }
  catch(...)
    {
    // She dumped us :(
    }
  }
小熊貓大暴走
小熊貓大暴走
翻譯於 3年前

1人頂

 翻譯的不錯哦!

代碼

附上的代碼包含所有上述我的所述。可以在visualstudio 2012 CTP版本的編譯器下編譯成功(除了tls機制)。(代碼地址:http://www.codeproject.com/KB/cpp/540912/c11threads.zip)

還有什麼

還有很多值得包括的事情,如:
  • 信號量(Semaphores)
  • 命名對象 (Named objects)
  • 進程共享對象 (Shareable objects across processes.)
  • ...

你應該做什麼呢?通常當編寫新的代碼,如果足夠適用,儘量選擇C++標準。對於已存在的代碼,我儘量保持使用win32調用,當需要移植它們到另外的平臺,我則會用c++11函數來實現CreateThread、SetEvent 等等。

發佈了62 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章