C++11多線程編程基礎入門

來源:互聯網
上載者:User

1.在C++11中建立新線程

  在每個c++應用程式中,都有一個預設的主線程,即main函數,在c++11中,我們可以通過建立std::thread類的對象來建立其他線程,每個std :: thread對象都可以與一個線程相關聯,只需包含標頭檔< thread>。可以使用std :: thread對象附加一個回調,當這個新線程啟動時,它將被執行。 這些回調可以為函數指標、函數對象、Lambda函數。
  線程對象可通過std::thread thObj(< CALLBACK>)來建立,新線程將在建立新對象後立即開始,並且將與已啟動的線程並存執行傳遞的回調。此外,任何線程可以通過在該線程的對象上調用join()函數來等待另一個線程退出。
  使用函數指標建立線程:

//main.cpp#include <iostream>#include <thread>void thread_function() {        for (int i = 0; i < 5; i++)            std::cout << "thread function excuting" << std::endl;}int main() {        std::thread threadObj(thread_function);        for (int i = 0; i < 5; i++)            std::cout << "Display from MainThread" << std::endl;   threadObj.join();       std::cout << "Exit of Main function" << std::endl;    return 0;}

  使用函數對象建立線程:

#include <iostream>#include <thread>class DisplayThread {    public:void operator ()() {                for (int i = 0; i < 100; i++)                    std::cout << "Display Thread Excecuting" << std::endl;    }};int main() {        std::thread threadObj((DisplayThread()));        for (int i = 0; i < 100; i++)            std::cout << "Display From Main Thread " << std::endl;        std::cout << "Waiting For Thread to complete" << std::endl;    threadObj.join();        std::cout << "Exiting from Main Thread" << std::endl;        return 0;}

CmakeLists.txt

cmake_minimum_required(VERSION 3.10)project(Thread_test)set(CMAKE_CXX_STANDARD 11)find_package(Threads REQUIRED)add_executable(Thread_test main.cpp)target_link_libraries(Thread_test ${CMAKE_THREAD_LIBS_INIT})

每個std::thread對象都有一個相關聯的id,std::thread::get_id() —-成員函數中給出對應線程對象的id;
std::this_thread::get_id()—-給出當前線程的id,如果std::thread對象沒有關聯的線程,get_id()將返回預設構造的std::thread::id對象:“not any thread”,std::thread::id也可以表示id。

2.joining和detaching 線程

線程一旦啟動,另一個線程可以通過調用std::thread對象上調用join()函數等待這個線程執行完畢:

std::thread threadObj(funcPtr); threadObj.join();

例如,主線程啟動10個線程,啟動完畢後,main函數等待他們執行完畢,join完所有線程後,main函數繼續執行:

#include <iostream>#include <thread>#include <algorithm>class WorkerThread{    public:void operator()(){                std::cout<<"Worker Thread "<<std::this_thread::get_id()<<"is Excecuting"<<std::endl;    }};    int main(){            std::vector<std::thread> threadList;            for(int i = 0; i < 10; i++){        threadList.push_back(std::thread(WorkerThread()));    }        // Now wait for all the worker thread to finish i.e.    // Call join() function on each of the std::thread object    std::cout<<"Wait for all the worker thread to finish"<<std::endl;        std::for_each(threadList.begin(), threadList.end(), std::mem_fn(&std::thread::join));        std::cout<<"Exiting from Main Thread"<<std::endl;        return 0;}

detach可以將線程與線程對象分離,讓線程作為後台線程執行,當前線程也不會阻塞了.但是detach之後就無法在和線程發生聯絡了.如果線程執行函數使用了臨時變數可能會出現問題,線程調用了detach在後台運行,臨時變數可能已經銷毀,那麼線程會訪問已經被銷毀的變數,需要在std::thread對象中調用std::detach()函數:

std::thread threadObj(funcPtr)threadObj.detach();

調用detach()後,std::thread對象不再與實際執行線程相關聯,線上程控制代碼上調用detach() 和 join()要小心.

3.將參數傳遞給線程

要將參數傳遞給線程的可關聯對象或函數,只需將參數傳遞給std::thread建構函式,預設情況下,所有的參數都將複製到新線程的內部儲存中。
給線程傳遞參數:

#include <iostream>#include <string>#include <thread>void threadCallback(int x, std::string str) {      std::cout << "Passed Number = " << x << std::endl;      std::cout << "Passed String = " << str << std::endl;}int main() {      int x = 10;      std::string str = "Sample String";      std::thread threadObj(threadCallback, x, str);  threadObj.join();    return 0;}

  給線程傳遞引用:

#include <iostream>#include <thread>void threadCallback(int const& x) {      int& y = const_cast<int&>(x);  y++;    std::cout << "Inside Thread x = " << x << std::endl;}int main() {      int x = 9;      std::cout << "In Main Thread : Before Thread Start x = " << x << std::endl;      std::thread threadObj(threadCallback, x);  threadObj.join();    std::cout << "In Main Thread : After Thread Joins x = " << x << std::endl;    return 0;}

輸出結果為:
In Main Thread : Before Thread Start x = 9
Inside Thread x = 10
In Main Thread : After Thread Joins x = 9

Process finished with exit code 0
即使threadCallback接受參數作為引用,但是並沒有改變main中x的值,線上程引用外它是不可見的。這是因為線程函數threadCallback中的x是引用複製在新線程的堆棧中的臨時值,使用std::ref可進行修改:

#include <iostream>#include <thread>void threadCallback(int const& x) {      int& y = const_cast<int&>(x);  y++;    std::cout << "Inside Thread x = " << x << std::endl;}int main() {      int x = 9;  std::cout << "In Main Thread : Before Thread Start x = " << x << std::endl;      std::thread threadObj(threadCallback, std::ref(x));  threadObj.join();    std::cout << "In Main Thread : After Thread Joins x = " << x << std::endl;    return 0;}

輸出結果為:
In Main Thread : Before Thread Start x = 9
Inside Thread x = 10
In Main Thread : After Thread Joins x = 10

Process finished with exit code 0
指定一個類的成員函數的指標作為線程函數,將指標傳遞給成員函數作為回呼函數,並將指標指向對象作為第二個參數:

#include <iostream>#include <thread>class DummyClass { public:  DummyClass() { }  DummyClass(const DummyClass& obj) { }    void sampleMemberfunction(int x) {          std::cout << "Inside sampleMemberfunction " << x << std::endl;  }};    int main() {      DummyClass dummyObj;        int x = 10;        std::thread threadObj(&DummyClass::sampleMemberfunction, &dummyObj, x);      threadObj.join();        return 0;}

4.線程間資料的共用與競爭條件

在多線程間的資料共用很簡單,但是在程式中的這種資料共用可能會引起問題,其中一種便是競爭條件。當兩個或多個線程並存執行一組操作,訪問相同的記憶體位置,此時,它們中的一個或多個線程會修改記憶體位置中的資料,這可能會導致一些意外的結果,這就是競爭條件。競爭條件通常較難發現並重現,因為它們並不總是出現,只有當兩個或多個線程執行操作的相對順序導致意外結果時,它們才會發生。
例如建立5個線程,這些線程共用類Wallet的一個對象,使用addMoney()成員函數並行添加100元。所以,如果最初錢包中的錢是0,那麼在所有線程的競爭執行完畢後,錢包中的錢應該是500,但是,由於所有線程同時修改共用資料,在某些情況下,錢包中的錢可能遠小於500。
測試如下:

#include <iostream>#include <thread>#include <algorithm>class Wallet {        int mMoney;    public: Wallet() : mMoney(0) { }        int getMoney() { return mMoney; }        void addMoney(int money) {                for (int i = 0; i < money; i++) {            mMoney++;        }    }};int testMultithreadWallet() {    Wallet walletObject;        std::vector<std::thread> threads;        for (int i = 0; i < 5; i++) {        threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100));    }    for (int i = 0; i < 5; i++) {        threads.at(i).join();    }        return walletObject.getMoney();}int main() {            int val = 0;            for (int k = 0; k < 100; k++) {                if ((val=testMultithreadWallet()) != 500) {                        std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;        }    }        return 0;}

每個線程並行地增加相同的成員變數“mMoney”,看似是一條線,但是這個“nMoney++”實際上被轉換為3條機器命令:
·在Register中載入”mMoney”變數
·增加register的值
·用register的值更新“mMoney”變數
在這種情況下,一個增量將被忽略,因為不是增加mMoney變數,而是增加不同的寄存器,“mMoney”變數的值被覆蓋。

5.使用mutex處理競爭條件

為了處理多線程環境中的競爭條件,我們需要mutex互斥鎖,在修改或讀取共用資料前,需要對資料加鎖,修改完成後,對資料進行解鎖。在c++11的線程庫中,mutexes在< mutexe >標頭檔中,表示互斥體的類是std::mutex。
就上面的問題進行處理,Wallet類提供了在Wallet中增加money的方法,並且在不同的線程中使用相同的Wallet對象,所以我們需要對Wallet的addMoney()方法加鎖。在增加Wallet中的money前加鎖,並且在離開該函數前解鎖,看代碼:Wallet類內部維護money,並提供函數addMoney(),這個成員函數首先擷取一個鎖,然後給wallet對象的money增加指定的數額,最後釋放鎖。

#include <iostream>#include <thread>#include <vector>#include <mutex>class Wallet {            int mMoney;            std::mutex mutex;public:    Wallet() : mMoney(0) { }        int getMoney() { return mMoney;}        void addMoney(int money) {        mutex.lock();                for (int i = 0; i < money; i++) {            mMoney++;        }        mutex.unlock();    }};int testMultithreadWallet() {    Wallet walletObject;        std::vector<std::thread> threads;        for (int i = 0; i < 5; ++i) {        threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 1000));    }    for (int i = 0; i < threads.size(); i++) {        threads.at(i).join();    }        return walletObject.getMoney();}int main() {            int val = 0;            for (int k = 0; k < 1000; k++) {                if ((val = testMultithreadWallet()) != 5000) {                    std::cout << "Error at count= " << k << " money in wallet" << val << std::endl;        }    }        return 0;}

這種情況保證了錢包裡的錢不會出現少於5000的情況,因為addMoney()中的互斥鎖確保了只有在一個線程修改完成money後,另一個線程才能對其進行修改,但是,如果我們忘記在函數結束後對鎖進行釋放會怎麼樣?這種情況下,一個線程將退出而不釋放鎖,其他線程將保持等待,為了避免這種情況,我們應當使用std::lock_guard,這是一個template class,它為mutex實現RALL,它將mutex包裹在其對象內,並將附加的mutex鎖定在其建構函式中,當其解構函式被調用時,它將釋放互斥體。

class Wallet {      int mMoney;      std::mutex mutex; public:  Wallet() : mMoney(0) { }  int getMoney() { return mMoney;}    void addMoney(int money) {      std::lock_guard<std::mutex> lockGuard(mutex);      for (int i = 0; i < mMoney; ++i) {        //如果在此處發生異常,lockGuadr的解構函式將會因為堆棧展開而被調用      mMoney++;            //一旦函數退出,那麼lockGuard對象的解構函式將被調用,在解構函式中mutex會被釋放    }  }};

6.條件變數

  條件變數是一種用於在2個線程之間進行信令的事件,一個線程可以等待它得到訊號,其他的線程可以給它發訊號。在c++11中,條件變數需要標頭檔< condition_variable>,同時,條件變數還需要一個mutex鎖。
  條件變數是如何啟動並執行:
  ·線程1調用等待條件變數,內部擷取mutex互斥鎖並檢查是否滿足條件;
  ·如果沒有,則釋放鎖,並等待條件變數得到發出的訊號(線程被阻塞),條件變數的wait()函數以原子方式提供這兩個操作;
  ·另一個線程,如線程2,當滿足條件時,向條件變數發訊號;
  ·一旦線程1正等待其恢複的條件變數發出訊號,線程1便擷取互斥鎖,並檢查與條件變數相關關聯的條件是否滿足,或者是否是一個上級調用,如果多個線程正在等待,那麼notify_one將只解鎖一個線程;
  ·如果是一個上級調用,那麼它再次調用wait()函數。
  條件變數的主要成員函數:
Wait()
它使得當前線程阻塞,直到條件變數得到訊號或發生虛假喚醒;
它原子性地釋放附加的mutex,阻塞當前線程,並將其添加到等待當前條件變數對象的線程列表中,當某線程在同樣的條件變數上調用notify_one() 或者 notify_all(),線程將被解除阻塞;
這種行為也可能是虛假的,因此,解除阻塞後,需要再次檢查條件;
一個回呼函數會傳給該函數,調用它來檢查其是否是虛假調用,還是確實滿足了真實條件;
當線程解除阻塞後,wait()函數擷取mutex鎖,並檢查條件是否滿足,如果條件不滿足,則再次原子性地釋放附加的mutex,阻塞當前線程,並將其添加到等待當前條件變數對象的線程列表中。
notify_one()
如果所有線程都在等待相同的條件變數對象,那麼notify_one會取消阻塞其中一個等待線程。
notify_all()
如果所有線程都在等待相同的條件變數對象,那麼notify_all會取消阻塞所有的等待線程。

#include <iostream>#include <thread>#include <functional>#include <mutex>#include <condition_variable>using namespace std::placeholders;class Application {        std::mutex m_mutex;        std::condition_variable m_condVar;        bool m_bDataLoaded;public:  Application() {        m_bDataLoaded = false;    }        void loadData() {                    //使該線程sleep 1秒        std::this_thread::sleep_for(std::chrono::milliseconds(1000));                std::cout << "Loading Data from XML" << std::endl;        //鎖定資料        std::lock_guard<std::mutex> guard(m_mutex);        //flag設為true,表明資料已載入        m_bDataLoaded = true;        //通知條件變數        m_condVar.notify_one();    }    bool isDataLoaded() {                     return m_bDataLoaded;    }    void mainTask() {                    std::cout << "Do some handshaking" << std::endl;        //擷取鎖        std::unique_lock<std::mutex> mlock(m_mutex);        //開始等待條件變數得到訊號        //wait()將在內部釋放鎖,並使線程阻塞        //一旦條件變數發出訊號,則恢複線程並再次擷取鎖        //然後檢測條件是否滿足,如果條件滿足,則繼續,否則再次進入wait        m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));                std::cout << "Do Processing On loaded Data" << std::endl;    }};int main() {    Application app;        std::thread thread_1(&Application::mainTask, &app);        std::thread thread_2(&Application::loadData, &app);    thread_2.join();    thread_1.join();    return 0;}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.