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多线程编程基本入门的细致内容,更多请关注ki4网别的相干文章!