1. 程式人生 > >C++11多線程教學

C++11多線程教學

阻塞 同步 blog 創建 linux系統中 展示 我們 形參 結構

轉自:http://www.cnblogs.com/lidabo/p/3908705.html

本篇教學代碼可在GitHub獲得:https://github.com/sol-prog/threads。

在之前的教學中,我展示了一些最新進的C++11語言內容:

  • 1. 正則表達式(http://solarianprogrammer.com/2011/10/12/cpp-11-regex-tutorial/)
  • 2. raw string(http://solarianprogrammer.com/2011/10/16/cpp-11-raw-strings-literals-tutorial/)
  • 3. lambda(http://solarianprogrammer.com/2011/11/01/cpp-11-lambda-tutorial/)

也許支持多線程是C++語言最大的變化之一。此前,C++只能利用操作系統的功能(Unix族系統使用pthreads庫),或是例如OpenMP和MPI這些代碼庫,來實現多核計算的目標。

本教程意圖讓你在使用C++11線程上起個頭,而不是只把語言標準在這裏繁復地羅列出來。

創建和啟動一條C++線程就像在C++源碼中添加線程頭文件那麽簡便。我們來看看如何創建一個簡單的帶線程的HelloWorld:

#include《iostream》

#include《thread》

//This function will be called from a thread

//該函數將在一條線程中得到調用

void call_from_thread() {

std::cout << "Hello, World" << std::endl;

}

int main() {

//Launch a thread

//啟動一條線程

std::thread t1(call_from_thread);

//Join the thread with the main thread

//和主線程協同

t1.join();

return 0;

}

在Linux系統中,上列代碼可采用g++編譯:

g++ -std=c++0x -pthread file_name.cpp

在安裝有Xcode4.x的麥金系統上,可用clang++編譯上述代碼:

clang++ -std=c++0x -stdlib=libc++ file_name.cpp

視窗系統上,可以利用付費代碼庫,just::thread,來編譯多線程代碼。但是很不走運,他們沒有提供代碼庫的試用版,我做不了測試。

在真實世界的應用程序中,函數“call_from_thread”相對主函數而言,獨立進行一些運算工作。在上述代碼中,主函數創建一條線程,並在t1.join()處等待t1線程運行結束。如果你在編碼中忘記考慮等待一條線程結束運行,主線程有可能搶先結束它自己的運行狀態,整個程序在退出的時候,將殺死先前創建的線程,不管函數“call_from_thread”有沒有執行完。

上面的代碼比使用POSIX線程的等價代碼,相對來說簡潔一些。請看使用POSIX線程的等價代碼:

//This function will be called from a thread

void *call_from_thread(void *) {

std::cout << "Launched by thread" << std::endl;

return NULL;

}

int main() {

pthread_t t;

//Launch a thread

pthread_create(&t, NULL, call_from_thread, NULL);

//Join the thread with the main thread

pthread_join(t, NULL);

return 0;

}

我們通常希望一次啟動多個線程,來並行工作。為此,我們可以創建線程組,而不是在先前的舉例中那樣創建一條線程。下面的例子中,主函數創建十條為一組的線程,並且等待這些線程完成他們的任務(在github代碼庫中也包含這個例子的POSIX版本):

...

static const int num_threads = 10;

...

int main() {

std::thread t[num_threads];

//Launch a group of threads 啟動一組線程

for (int i = 0; i < num_threads; ++i) {

t[i] = std::thread(call_from_thread);

}

std::cout << "Launched from the mainn";

//Join the threads with the main thread

for (int i = 0; i < num_threads; ++i) {

t[i].join();

}

return 0;

}

記住,主函數也是一條線程,通常叫做主線程,所以上面的代碼實際上有11條線程在運行。在啟動這些線程組之後,線程組和主函數進行協同(join)之前,允許我們在主線程中做些其他的事情,在教程的結尾部分,我們將會用一個圖像處理的例子來說明之。

在線程中使用帶有形參的函數,是怎麽一回事呢?C++11允許我們在線程的調用中,附帶上所需的任意參數。為了舉例說明,我們可以修改上面的代碼,以接受一個整型參數(在github代碼庫中也包含這個例子的POSIX版本):

static const int num_threads = 10;

//This function will be called from a thread

void call_from_thread(int tid) {

std::cout << "Launched by thread " << tid << std::endl;

}

int main() {

std::thread t[num_threads];

//Launch a group of threads

for (int i = 0; i < num_threads; ++i) {

t[i] = std::thread(call_from_thread, i);

}

std::cout << "Launched from the mainn";

//Join the threads with the main thread

for (int i = 0; i < num_threads; ++i) {

t[i].join();

}

return 0;

}

在我的系統上,上面代碼的執行結果是:

Sol$ ./a.out

Launched by thread 0

Launched by thread 1

Launched by thread 2

Launched from the main

Launched by thread 3

Launched by thread 5

Launched by thread 6

Launched by thread 7

Launched by thread Launched by thread 4

8L

aunched by thread 9

Sol$

能看到上面的結果中,程序一旦創建一條線程,其運行存在先後秩序不確定的現象。程序員的任務就是要確保這組線程在訪問公共數據時不要出現阻塞。最後幾行,所顯示的錯亂輸出,表明8號線程啟動的時候,4號線程還沒有完成在stdout上的寫操作。事實上假定在你自己的機器上運行上面的代碼,將會獲得全然不同的結果,甚至是會輸出些混亂的字符。原因在於,程序內的11條線程都在競爭性地使用stdout這個公共資源(案:Race Conditions)。

要避免上面的問題,可以在代碼中使用攔截器(barriers),如std:mutex,以同步(synchronize)的方式來使得一群線程訪問公共資源,或者,如果可行的話,為線程們預留下私用的數據結構,避免使用公共資源。我們在以後的教學中,還會講到線程同步問題,包括使用原子操作類型(atomic types)和互斥體(mutex)。

從原理上講,編寫更加復雜的並行代碼所需的概念,我們已經在上面的代碼中都談到了。

如果有興趣學習新的C++11語法,我建議閱讀《Professional C++》,或《C ++ Primer Plus》。C++11多線程主題方面,建議閱讀《C++ Concurrency in Action》,這是一本好書。

C++11多線程教學