1. 程式人生 > >C++11多執行緒程式設計基礎入門

C++11多執行緒程式設計基礎入門

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

相關推薦

C++11執行程式設計基礎入門

1.在C++11中建立新執行緒   在每個c++應用程式中,都有一個預設的主執行緒,即main函式,在c++11中,我們可以通過建立std::thread類的物件來建立其他執行緒,每個std :: thread物件都可以與一個執行緒相關聯,只需包含標頭檔案&l

C++11執行程式設計 緒論及總結

C++11多執行緒程式設計 這一系列文章是從 https://thispointer.com/c11-multithreading-tutorial-series/ 轉過來的, 本來想翻譯一下, 但看了些內容, 用詞都不難, 讀英文沒有太大難度, 翻譯過來反而怕用詞不準畫蛇添

C++11執行程式設計 第十章: 使用packaged_task優雅的讓同步函式非同步執行

C++11 Multithreading – Part 10: packaged_task<> Example and Tutorial Varun July 2, 2017 C++11 Multithreading – Part 10: packaged_tas

C++11執行程式設計 第九章: std::async 更更優雅的寫執行

C++11 Multithreading – Part 9: std::async Tutorial & Example Varun May 5, 2017 C++11 Multithreading – Part 9: std::async Tutorial &

C++11執行程式設計 第八章: 使用 std::future std::promise 更優雅的獲取執行返回值

C++11 Multithreading – Part 8: std::future , std::promise and Returning values from Thread Varun June 20, 2015 C++11 Multithreading – Part

C++11執行程式設計 第七章: 條件變數及其使用方法

C++11 Multithreading – Part 7: Condition Variables Explained Varun June 2, 2015 C++11 Multithreading – Part 7: Condition Variables Explain

C++11執行程式設計 第五章: 使用鎖來解決竟態條件

C++11 Multithreading – Part 5: Using mutex to fix Race Conditions Varun February 22, 2015 C++11 Multithreading – Part 5: Using mutex to fi

C++11執行程式設計 第四章: 共享資料和競態條件

C++11 Multithreading – Part 4: Data Sharing and Race Conditions Varun February 21, 2015C++11 Multithreading – Part 4: Data Sharing and Race Con

C++11執行程式設計 第三章: 如何向執行傳參

C++11 Multithreading – Part 3: Carefully Pass Arguments to Threads Varun January 22, 2015 C++11 Multithreading – Part 3: Carefully Pass Ar

C++11執行程式設計 第二章: join 和 detach 執行

  C++11 Multithreading – Part 2: Joining and Detaching Threads Varun January 21, 2015 C++11 Multithreading – Part 2: Joining and De

C++11執行程式設計 第六章: 執行之間的事件處理

C++11 Multithreading – Part 6: Need of Event Handling Varun June 1, 2015 C++11 Multithreading – Part 6: Need of Event Handling2018-08-18T1

C++11 執行程式設計使用例項

最新研究下C++11中執行緒的知識,基本內容如下: 1、C++11中建立執行緒的幾種方式 在C11中,我們可以通過建立std::thread類的物件來建立額外的執行緒。每個thread物件可以跟具體的某個執行緒關聯,從而達到多執行緒併發的目的。  必須 #include 

C++11執行程式設計系列(二)實戰

C++11 新標準中引入了多個頭檔案來支援多執行緒程式設計,他們分別是<atomic> ,<thread>,<mutex>,<condition_variable>和<future>。 <

c++11執行程式設計-程序與執行

概念:        程序:第一,程序是一個實體。每一個程序都有它自己的地址空間,一般情況下,包括文字區域(text  region)、資料區域(data region)和堆疊(stack

C++11 執行程式設計

1.利用C++11執行緒函式建立執行緒, #include<iostream> #include<thread> using namespace std; void fun

c++11執行程式設計(二):joining和detaching 執行

Joining執行緒 執行緒一旦啟動,另一個執行緒可以通過呼叫std::thread物件上呼叫join()函式等待這個執行緒執行完畢std::thread th(funcPtr); th.join(); 看一個例子主執行緒啟動10個工作執行緒,啟動完畢後,main函式等待

c++11執行程式設計引數傳遞若干問題

 隨著計算機處理器多核的出現,程式設計師編寫多執行緒的需求越來越大。當處理互相獨立的任務時,我們可以更好的使用多核的多執行緒的效率,可以很大的提高執行速度,但是有時候提高的速度並不是成倍的提高,因為有

c++11執行程式設計(四):資料共享和競爭條件

在多執行緒環境中,執行緒間的資料共享很簡單,但是在程式中這種簡單的資料共享可能會引起問題,其中一種便是競爭條件。什麼是競爭條件? 競賽條件是發生在多執行緒應用程式中的一種bug 當兩個或多個執行緒並行執行一組操作,訪問相同的記憶體位置,此時,它們中的一個或多個執行緒會修改記

c++11執行程式設計(一):建立執行的三種方法

c++11執行緒庫原始的c++標準僅支援單執行緒程式設計,新的c++標準(c++11或c++0x)於2011年釋出,引入了新的執行緒庫。 編譯器要求 Linux: gcc 4.8.1 (完全併發支援) Windows: Visual Studio 2012 and Min

C++11併發程式設計(一)——初始C++11執行

1 前言   C++11標準在標準庫中為多執行緒提供了元件,這意味著使用C++編寫與平臺無關的多執行緒程式成為可能,而C++程式的可移植性也得到了有力的保證。   在之前我們主要使用的多執行緒庫要麼