本篇內容介紹了“C++11多線程編程的方法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
1.在C++11中創建新線程
在每個c++應用程序中,都有一個默認的主線程,即main函數,在c++11中,我們可以通過創建std::thread類的對象來創建其他線程,每個std :: thread對象都可以與一個線程相關聯,只需包含頭文件< thread>??梢允褂胹td :: 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。
線程一旦啟動,另一個線程可以通過調用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()要小心.
要將參數傳遞給線程的可關聯對象或函數,只需將參數傳遞給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;
}在多線程間的數據共享很簡單,但是在程序中的這種數據共享可能會引起問題,其中一種便是競爭條件。當兩個或多個線程并行執行一組操作,訪問相同的內存位置,此時,它們中的一個或多個線程會修改內存位置中的數據,這可能會導致一些意外的結果,這就是競爭條件。競爭條件通常較難發現并重現,因為它們并不總是出現,只有當兩個或多個線程執行操作的相對順序導致意外結果時,它們才會發生。
例如創建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”變量的值被覆蓋。
為了處理多線程環境中的競爭條件,我們需要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會被釋放
}
}
}; 條件變量是一種用于在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多線程編程的方法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。