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++程式的可移植性也得到了有力的保證。 在之前我們主要使用的多執行緒庫要麼