溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C++11多線程編程的示例分析

發(fā)布時(shí)間:2021-06-11 13:48:26 來源:億速云 閱讀:395 作者:小新 欄目:編程語言

小編給大家分享一下C++11多線程編程的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

1.在C++11中創(chuàng)建新線程

  在每個(gè)c++應(yīng)用程序中,都有一個(gè)默認(rèn)的主線程,即main函數(shù),在c++11中,我們可以通過創(chuàng)建std::thread類的對(duì)象來創(chuàng)建其他線程,每個(gè)std :: thread對(duì)象都可以與一個(gè)線程相關(guān)聯(lián),只需包含頭文件< thread>??梢允褂胹td :: thread對(duì)象附加一個(gè)回調(diào),當(dāng)這個(gè)新線程啟動(dòng)時(shí),它將被執(zhí)行。 這些回調(diào)可以為函數(shù)指針、函數(shù)對(duì)象、Lambda函數(shù)。
  線程對(duì)象可通過std::thread thObj(< CALLBACK>)來創(chuàng)建,新線程將在創(chuàng)建新對(duì)象后立即開始,并且將與已啟動(dòng)的線程并行執(zhí)行傳遞的回調(diào)。此外,任何線程可以通過在該線程的對(duì)象上調(diào)用join()函數(shù)來等待另一個(gè)線程退出。
  使用函數(shù)指針創(chuàng)建線程:

//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;
}

  使用函數(shù)對(duì)象創(chuàng)建線程:

#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})

每個(gè)std::thread對(duì)象都有一個(gè)相關(guān)聯(lián)的id,std::thread::get_id() —-成員函數(shù)中給出對(duì)應(yīng)線程對(duì)象的id;
std::this_thread::get_id()—-給出當(dāng)前線程的id,如果std::thread對(duì)象沒有關(guān)聯(lián)的線程,get_id()將返回默認(rèn)構(gòu)造的std::thread::id對(duì)象:“not any thread”,std::thread::id也可以表示id。

2.joining和detaching 線程

線程一旦啟動(dòng),另一個(gè)線程可以通過調(diào)用std::thread對(duì)象上調(diào)用join()函數(shù)等待這個(gè)線程執(zhí)行完畢:

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

例如,主線程啟動(dòng)10個(gè)線程,啟動(dòng)完畢后,main函數(shù)等待他們執(zhí)行完畢,join完所有線程后,main函數(shù)繼續(xù)執(zhí)行:

#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可以將線程與線程對(duì)象分離,讓線程作為后臺(tái)線程執(zhí)行,當(dāng)前線程也不會(huì)阻塞了.但是detach之后就無法在和線程發(fā)生聯(lián)系了.如果線程執(zhí)行函數(shù)使用了臨時(shí)變量可能會(huì)出現(xiàn)問題,線程調(diào)用了detach在后臺(tái)運(yùn)行,臨時(shí)變量可能已經(jīng)銷毀,那么線程會(huì)訪問已經(jīng)被銷毀的變量,需要在std::thread對(duì)象中調(diào)用std::detach()函數(shù):

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

調(diào)用detach()后,std::thread對(duì)象不再與實(shí)際執(zhí)行線程相關(guān)聯(lián),在線程句柄上調(diào)用detach() 和 join()要小心.

3.將參數(shù)傳遞給線程

要將參數(shù)傳遞給線程的可關(guān)聯(lián)對(duì)象或函數(shù),只需將參數(shù)傳遞給std::thread構(gòu)造函數(shù),默認(rèn)情況下,所有的參數(shù)都將復(fù)制到新線程的內(nèi)部存儲(chǔ)中。
給線程傳遞參數(shù):

#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;
}

輸出結(jié)果為:
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接受參數(shù)作為引用,但是并沒有改變main中x的值,在線程引用外它是不可見的。這是因?yàn)榫€程函數(shù)threadCallback中的x是引用復(fù)制在新線程的堆棧中的臨時(shí)值,使用std::ref可進(jìn)行修改:

#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;
}

輸出結(jié)果為:
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
指定一個(gè)類的成員函數(shù)的指針作為線程函數(shù),將指針傳遞給成員函數(shù)作為回調(diào)函數(shù),并將指針指向?qū)ο笞鳛榈诙€(gè)參數(shù):

#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.線程間數(shù)據(jù)的共享與競(jìng)爭(zhēng)條件

在多線程間的數(shù)據(jù)共享很簡(jiǎn)單,但是在程序中的這種數(shù)據(jù)共享可能會(huì)引起問題,其中一種便是競(jìng)爭(zhēng)條件。當(dāng)兩個(gè)或多個(gè)線程并行執(zhí)行一組操作,訪問相同的內(nèi)存位置,此時(shí),它們中的一個(gè)或多個(gè)線程會(huì)修改內(nèi)存位置中的數(shù)據(jù),這可能會(huì)導(dǎo)致一些意外的結(jié)果,這就是競(jìng)爭(zhēng)條件。競(jìng)爭(zhēng)條件通常較難發(fā)現(xiàn)并重現(xiàn),因?yàn)樗鼈儾⒉豢偸浅霈F(xiàn),只有當(dāng)兩個(gè)或多個(gè)線程執(zhí)行操作的相對(duì)順序?qū)е乱馔饨Y(jié)果時(shí),它們才會(huì)發(fā)生。
例如創(chuàng)建5個(gè)線程,這些線程共享類Wallet的一個(gè)對(duì)象,使用addMoney()成員函數(shù)并行添加100元。所以,如果最初錢包中的錢是0,那么在所有線程的競(jìng)爭(zhēng)執(zhí)行完畢后,錢包中的錢應(yīng)該是500,但是,由于所有線程同時(shí)修改共享數(shù)據(jù),在某些情況下,錢包中的錢可能遠(yuǎn)小于500。
測(cè)試如下:

#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;
}

每個(gè)線程并行地增加相同的成員變量“mMoney”,看似是一條線,但是這個(gè)“nMoney++”實(shí)際上被轉(zhuǎn)換為3條機(jī)器命令:
·在Register中加載”mMoney”變量
·增加register的值
·用register的值更新“mMoney”變量
在這種情況下,一個(gè)增量將被忽略,因?yàn)椴皇窃黾觤Money變量,而是增加不同的寄存器,“mMoney”變量的值被覆蓋。

5.使用mutex處理競(jìng)爭(zhēng)條件

為了處理多線程環(huán)境中的競(jìng)爭(zhēng)條件,我們需要mutex互斥鎖,在修改或讀取共享數(shù)據(jù)前,需要對(duì)數(shù)據(jù)加鎖,修改完成后,對(duì)數(shù)據(jù)進(jìn)行解鎖。在c++11的線程庫中,mutexes在< mutexe >頭文件中,表示互斥體的類是std::mutex。
就上面的問題進(jìn)行處理,Wallet類提供了在Wallet中增加money的方法,并且在不同的線程中使用相同的Wallet對(duì)象,所以我們需要對(duì)Wallet的addMoney()方法加鎖。在增加Wallet中的money前加鎖,并且在離開該函數(shù)前解鎖,看代碼:Wallet類內(nèi)部維護(hù)money,并提供函數(shù)addMoney(),這個(gè)成員函數(shù)首先獲取一個(gè)鎖,然后給wallet對(duì)象的money增加指定的數(shù)額,最后釋放鎖。

#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;
}

這種情況保證了錢包里的錢不會(huì)出現(xiàn)少于5000的情況,因?yàn)閍ddMoney()中的互斥鎖確保了只有在一個(gè)線程修改完成money后,另一個(gè)線程才能對(duì)其進(jìn)行修改,但是,如果我們忘記在函數(shù)結(jié)束后對(duì)鎖進(jìn)行釋放會(huì)怎么樣?這種情況下,一個(gè)線程將退出而不釋放鎖,其他線程將保持等待,為了避免這種情況,我們應(yīng)當(dāng)使用std::lock_guard,這是一個(gè)template class,它為mutex實(shí)現(xiàn)RALL,它將mutex包裹在其對(duì)象內(nèi),并將附加的mutex鎖定在其構(gòu)造函數(shù)中,當(dāng)其析構(gòu)函數(shù)被調(diào)用時(shí),它將釋放互斥體。

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) {      
  //如果在此處發(fā)生異常,lockGuadr的析構(gòu)函數(shù)將會(huì)因?yàn)槎褩U归_而被調(diào)用
      mMoney++;      
      //一旦函數(shù)退出,那么lockGuard對(duì)象的析構(gòu)函數(shù)將被調(diào)用,在析構(gòu)函數(shù)中mutex會(huì)被釋放
    }

  }
};

6.條件變量

  條件變量是一種用于在2個(gè)線程之間進(jìn)行信令的事件,一個(gè)線程可以等待它得到信號(hào),其他的線程可以給它發(fā)信號(hào)。在c++11中,條件變量需要頭文件< condition_variable>,同時(shí),條件變量還需要一個(gè)mutex鎖。
  條件變量是如何運(yùn)行的:
  ·線程1調(diào)用等待條件變量,內(nèi)部獲取mutex互斥鎖并檢查是否滿足條件;
  ·如果沒有,則釋放鎖,并等待條件變量得到發(fā)出的信號(hào)(線程被阻塞),條件變量的wait()函數(shù)以原子方式提供這兩個(gè)操作;
  ·另一個(gè)線程,如線程2,當(dāng)滿足條件時(shí),向條件變量發(fā)信號(hào);
  ·一旦線程1正等待其恢復(fù)的條件變量發(fā)出信號(hào),線程1便獲取互斥鎖,并檢查與條件變量相關(guān)關(guān)聯(lián)的條件是否滿足,或者是否是一個(gè)上級(jí)調(diào)用,如果多個(gè)線程正在等待,那么notify_one將只解鎖一個(gè)線程;
  ·如果是一個(gè)上級(jí)調(diào)用,那么它再次調(diào)用wait()函數(shù)。
  條件變量的主要成員函數(shù):
Wait()
它使得當(dāng)前線程阻塞,直到條件變量得到信號(hào)或發(fā)生虛假喚醒;
它原子性地釋放附加的mutex,阻塞當(dāng)前線程,并將其添加到等待當(dāng)前條件變量對(duì)象的線程列表中,當(dāng)某線程在同樣的條件變量上調(diào)用notify_one() 或者 notify_all(),線程將被解除阻塞;
這種行為也可能是虛假的,因此,解除阻塞后,需要再次檢查條件;
一個(gè)回調(diào)函數(shù)會(huì)傳給該函數(shù),調(diào)用它來檢查其是否是虛假調(diào)用,還是確實(shí)滿足了真實(shí)條件;
當(dāng)線程解除阻塞后,wait()函數(shù)獲取mutex鎖,并檢查條件是否滿足,如果條件不滿足,則再次原子性地釋放附加的mutex,阻塞當(dāng)前線程,并將其添加到等待當(dāng)前條件變量對(duì)象的線程列表中。
notify_one()
如果所有線程都在等待相同的條件變量對(duì)象,那么notify_one會(huì)取消阻塞其中一個(gè)等待線程。
notify_all()
如果所有線程都在等待相同的條件變量對(duì)象,那么notify_all會(huì)取消阻塞所有的等待線程。

#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;        //鎖定數(shù)據(jù)
        std::lock_guard<std::mutex> guard(m_mutex);        //flag設(shè)為true,表明數(shù)據(jù)已加載
        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);        //開始等待條件變量得到信號(hào)
        //wait()將在內(nèi)部釋放鎖,并使線程阻塞
        //一旦條件變量發(fā)出信號(hào),則恢復(fù)線程并再次獲取鎖
        //然后檢測(cè)條件是否滿足,如果條件滿足,則繼續(xù),否則再次進(jìn)入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;
}

以上是“C++11多線程編程的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI