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。
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();
#3.將參數傳遞給執行緒
傳遞參數給執行緒:
#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 : After Thread Joins x = 9
即使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 : After Thread Joins x = 10
#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.執行緒間資料的共享與競爭條件
在多執行緒間的資料共享很簡單,但是在程式中的這種資料共享可能會造成問題,其中一種便是競爭條件。當兩個或多個執行緒並行執行一組操作,存取相同的記憶體位置,此時,它們中的一個或多個執行緒會修改記憶體位置中的數據,這可能會導致一些意外的結果,這就是競爭條件。競爭條件通常較難發現並重現,因為它們並不總是出現,只有當兩個或多個執行緒執行操作的相對順序導致意外結果時,它們才會發生。
測試如下:
#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; }
为了处理多线程环境中的竞争条件,我们需要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多執行緒程式設計基礎入門的詳細內容。更多資訊請關注PHP中文網其他相關文章!