집 >백엔드 개발 >C#.Net 튜토리얼 >C++11 다중 스레드 프로그래밍 기본 소개
1. C++11에서 새 스레드를 만듭니다.
모든 C++ 애플리케이션에는 기본 메인 스레드인 기본 함수가 있습니다. C+++11에서는 다음을 수행할 수 있습니다. std::thread 클래스의 객체를 생성하여 다른 스레드를 생성합니다. 각 std::thread 객체는 스레드와 연관될 수 있으며 헤더 파일 61fe42cd48946e53c78c0e2bbfbc7b04만 포함하면 됩니다. std::thread 객체를 사용하여 이 새 스레드가 시작될 때 실행될 콜백을 연결할 수 있습니다. 이러한 콜백은 함수 포인터, 함수 객체 또는 Lambda 함수일 수 있습니다.
스레드 개체는 std::thread thObj(
함수 포인터를 사용하여 스레드 만들기:
//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})
Each std:: 스레드 개체 관련 ID인 std::thread::get_id()가 있습니다. —-해당 스레드 개체의 ID는 멤버 함수에 제공됩니다.
std::this_thread::get_id() —-id입니다. 현재 스레드의 정보가 제공됩니다. std::thread 객체에 연결된 스레드가 없으면 get_id()는 기본으로 구성된 std::thread::id 객체를 반환합니다. "어떤 스레드도 아님", std::thread::id는 다음과 같습니다. id를 나타내기도 합니다.
스레드가 시작되면 다른 스레드는 std::에서 Join() 함수를 호출하여 이 스레드의 실행이 완료될 때까지 기다릴 수 있습니다. thread object:# 🎜🎜#
std::thread threadObj(funcPtr); threadObj.join();예를 들어, 메인 스레드는 10개의 스레드를 시작합니다. 시작된 후 메인 함수는 스레드가 모두 결합된 후 실행이 완료될 때까지 기다립니다. #🎜🎜 #
#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 OK 스레드 개체에서 스레드를 분리하고 스레드가 백그라운드 스레드로 실행되도록 합니다. 그러나 분리 후에는 더 이상 스레드에 연결할 수 없습니다. 스레드 실행 함수가 임시 변수를 사용하면 문제가 발생할 수 있습니다. 스레드 호출 Detach가 백그라운드에서 실행되고 임시 변수가 삭제되었을 수 있습니다. 그러면 스레드가 삭제된 변수에 액세스하고 std::detach()를 호출해야 합니다. std::thread 객체의 함수:
std::thread threadObj(funcPtr) threadObj.detach();
Call 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; }출력 결과는 다음과 같습니다.
메인 스레드에서: 스레드 시작 전 x = 9
내부 스레드 x = 10메인 스레드에서: 스레드 조인 후 x = 9
종료 코드 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; }
출력 결과는 다음과 같습니다. #🎜🎜 #메인 스레드에서 : 스레드 시작 전 x = 9
내부 스레드 x = 10
종료 코드 0으로 프로세스 완료 #🎜 🎜#Specify 클래스의 멤버 함수에 대한 포인터는 스레드 함수로 사용되고 포인터는 콜백 함수로 멤버 함수에 전달되며 포인터는 두 번째 매개변수로 개체를 가리킵니다.
#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위안을 병렬로 추가합니다. 따라서 처음에 지갑에 있는 Money가 0이라면 모든 스레드의 경쟁 실행이 완료된 후 지갑에 있는 Money는 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" 변수를 로드합니다.
·레지스터의 값을 증가시킵니다.
·"mMoney" 변수를
为了处理多线程环境中的竞争条件,我们需要mutex互斥锁,在修改或读取共享数据前,需要对数据加锁,修改完成后,对数据进行解锁。在c++11的线程库中,mutexes在eed5423f7254a06cc4264105c2e6fe37头文件中,表示互斥体的类是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中,条件变量需要头文件87d06fb90893e2a96c75139ed482c1e2,同时,条件变量还需要一个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 중국어 웹사이트의 기타 관련 기사를 참조하세요!