1. 程式人生 > >C++11並發編程

C++11並發編程

c++並發編程

C++11開始支持多線程編程,之前多線程編程都需要系統的支持,在不同的系統下創建線程需要不同的API如pthread_create(),Createthread(),beginthread()等。現在C++11中引入了一個新的線程庫,C++11提供了新頭文件,主要包含 <thread>、<mutex>、<atomic>、<condition_varible>、<future>五個部分;<thread>等用於支持多線程,同時包含了用於啟動、管理線程的諸多工具,同時,該庫還提供了包括像互斥量、鎖、原子量等在內的同步機制。在這個系列的文章中,我將嘗試和大家分享這個新庫提供的部分特性

為了讓你的代碼支持多線程庫,你環境上的編譯器必須支持 C++11,不過幸運的是現在絕大部分的編譯器都支持C++11了,除非你手頭上的編譯器版本是古董級別了,真這樣的話,還是建議你趕快升級,不光是為了這次的學習,後面你肯定會用上的。

多線程編程的好處

在看本文之前,如果對什麽是線程、什麽是進程仍存有疑惑,請先Google之,這兩個概念不在本文的範圍之內。用多線程只有一個目的,那就是更好的利用cpu的資源,下面兩個圖很好的反應了這點。

技術分享圖片

技術分享圖片

以單cpu為例,假如有個進程,進程功能是從網絡上接受數據,將接收到的數據進行處理。我們知道,網絡上的數據是有時延的,甚至在你接收數據的這段時間內由於網絡等問題接收不到數據,如果是單線程的進程

,那麽此時你的進程什麽也做不了,苦苦的在等待對端的數據。此時cpu被浪費了(假設系統就這一個進程,不考慮將進程切換到其它進程的)。

如果進程含有兩個線程,一個線程專門接收數據,暫且叫做A線程吧;另外一個線程專門負責處理接收的數據,就叫B線程吧。那麽情況就不一樣了,當由於網絡等問題接收不到數據時,阻塞的只是A線程,完全可以通過某種調度機制將cpu調度給B線程讓其工作。這樣,cpu得到了最大的利用而不至於浪費在那兒。

好了,既然線程的用處這麽大,C++11又加了支持多線程的庫,那麽讓我們一窺其廬山真面目吧。

如何啟動一個線程

在C++11中,啟動一個新的線程非常簡單,當你創建一個 std::thread 的實例時,它便會自行啟動。同時在創建線程實例時,須提供給該線程一段將要執行的函數,方法之一是在創建線程實例傳遞一個函數指針。

好吧,不管學什麽編程語言,Hello world!感覺總是不會少的,這仿佛已經成為了一個標準。這次我們仍以經典的 "Hello world” 為例來向大家如何創建一個線程:

#include <thread>
#include <iostream>

void hello()
{
    std::cout << "Hello world from thread !"               << std::endl;
}

int main()
{
    std::thread t(hello);
    t.join();

    return 0;
}
[root@xiaojianping c++]# g++ -std=c++11 -o a a.cpp
[root@xiaojianping c++]# ./a
terminate called after throwing an instance of 'std::system_error'
  what():  Enable multithreading to use std::thread: Operation not permitted
已放棄

雲行時報錯了,編譯的時候加上-lpthread

[root@xiaojianping c++]# g++ -std=c++11 -lpthread -o a a.cpp
[root@xiaojianping c++]# ./a
Hello world from thread !

看到了吧,首先,我們引入了頭文件<thread>。在這個頭文件中,C++11 提供了管理線程的類和函數。之後,我們定義了一個無返回類型的函數hello,這個函數除了在標準輸出流中打印一行文字之外什麽也不做。而後,我們定義了一個 std::thread 類型的變量 t,在定義的時候用hello函數名(其實就是一個函數指針)作為 std::thread 類構造函數的參數 。

這個例子中值得註意的是函數 join()。調用join()將導致當前線程等待被 join 的線程結束(在本例中即線程 main 必須等待線程 t結束後方可繼續執行)。如果你不調用 join() ,其結果是未定義的 —— 程序可能如你所願打印出 "Hello world from thread" 以及一個換行,但是也可能只打印出 "Hello world from thread" 卻沒有換行,甚至什麽都不做,那是因為線程 main 可能在線程 t結束之前就返回了。

其次還需註意的就是在編譯的時候需加上參數-std=c++11,否則編譯不過。

使用Lambda表達式啟動線程

如果線程所要執行的代碼非常短小時,你完全沒必要專門為之創建一個函數,取而代之的是使用 Lambda 表達式(關於Lambda 表達式,有興趣的朋友可以上網查查它的用法,它也是C++11新加入的特性)。我們可以很輕易地將上述例子改寫為使用 Lambda 表達式的形式:


#include <thread>
#include <iostream>

int main()
{
    std::thread t([](){
       std::cout << "Hello world from thread                !"<< std::endl;});
       t.join();

    return 0;
}

[root@xiaojianping c++]# ./b

Hello world from thread !

如上,我們使用了 Lambda 表達式替換掉原來的函數指針。不需要任何懷疑,這段代碼和之前使用函數指針的代碼實現了完全相同的功能。

給你的線程執行函數加上參數

是不是覺得開啟一個線程就是為了打印一句話有點小題大做是吧,好的,我們現在是該做點什麽了。在上個線程函數的基礎上,除了打印一句話,我們再加個功能:接受兩個參數,計算它們的和並打印出。

#include <thread>
#include <iostream>


//線程執行函數接受兩個int類型參數
void hello(int x,int y)
{
   std::cout<<"Hello world from thread !" <<std::endl;
   std::cout<<"x+y=" <<x+y <<std::endl;
}

int main()
{
  std::thread t (hello,10,20);
  t.join();
  return 0;
}
[root@xiaojianping c++]# ./c
Hello world from thread !
x+y=30

程序的運行結果正是我們想要的,沒錯,向線程函數傳參就是這麽簡單:在創建線程實例的時候和函數指針一起傳遞過去

區分線程

我們可以回一下我們是如何區分進程的?每個進程都有一個編號,稱為pid(進程id),我們就是通過它來區分不同的進程,不光是我們人,其實操作系統也是通過這個pid來區分管理不同的進程。

線程也一樣, 每個線程都有唯一的 ID 以便我們加以區分,我們稱之為tid(線程id)。使用 std::this_thread 類的 get_id() 便可獲取對應線程的tid。下面的例子將創建一些線程並使用 std::this_thread::get_id() 來獲取當前線程的tid,並打印出:

#include <thread>
#include <iostream>
#include <vector>

void hello()
{   
    std::cout << "Hello world from thread: "               << std::this_thread::get_id()               << std::endl;
}

int main()
{
    std::vector<std::thread> threads;
    for(int i = 0; i < 6; ++i)
    { 
        threads.push_back(std::thread(hello));
    }
    for(auto& thread : threads)
    {
        thread.join();
    }

   return 0;
}
[root@xiaojianping c++]# ./thread
Hello world from thread: Hello world from thread: Hello world from thread: Hello world from thread: 140598791485184
140598816663296140598799877888

Hello world from thread: 140598783092480
Hello world from thread: 140598774699776
140598808270592
[root@xiaojianping c++]# ./thread
Hello world from thread: 140198417712896
Hello world from thread: 140198409320192
Hello world from thread: 140198392534784
Hello world from thread: 140198375749376
Hello world from thread: 140198384142080
Hello world from thread: 140198400927488

看到沒,上述結果我肯定不是你所希望看到的,結果似乎混亂了,如果你在你的電腦上多運行幾遍,甚至還會出現其它各種結果,那麽這種匪夷所思的運行結果究竟是什麽原因導致的?

我們知道線程之間是交叉運行的,在上面這個例子,我們並沒有去控制線程的執行順序,某個線程在運行期間可能隨時被搶占, 同時可以看到,上面例子的ostream 分幾個步驟(首先輸出一個 string,然後是 ID,最後輸出換行),因此三個線程可能先都只執行了第一個步驟將 Hello world from thread 打印出來,然後每個線程都依次執行完剩余的兩個步驟(打印ID,然後換行),這就導致了上面的運行結果。

那麽有沒有方法解決上面的問題了,答案是肯定的,接下來我們將看到如何使用鎖機制控制線程的執行順序

#include <thread>
#include <iostream>
#include <vector>

pthread_mutex_t  lock = PTHREAD_MUTEX_INITIALIZER;

void hello()
{   
    pthread_mutex_lock(&lock);       
    std::cout << "Hello world from thread: "               << std::this_thread::get_id()               << std::endl;
    pthread_mutex_unlock(&lock);
}

int main()
{
    std::vector<std::thread> threads;
    for(int i = 0; i < 6; ++i)
    { 
        //pthread_mutex_lock(&lock);
        threads.push_back(std::thread(hello));
        //pthread_mutex_unlock(&lock);
    }
    for(auto& thread : threads)
    {
        thread.join();
    }

   return 0;
}

[root@xiaojianping c++]# ./thread
Hello world from thread: 140515972613888
Hello world from thread: 140515939043072
Hello world from thread: 140515930650368
Hello world from thread: 140515964221184
Hello world from thread: 140515955828480
Hello world from thread: 140515947435776
[root@xiaojianping c++]# ./thread
Hello world from thread: 140654524454656
Hello world from thread: 140654532847360
Hello world from thread: 140654516061952
Hello world from thread: 140654507669248
Hello world from thread: 140654499276544
Hello world from thread: 140654490883840

看到沒有,現在再執行thread程序的時候,就不會亂了。

轉載了碼農有道,並把後部分完成了。


C++11並發編程