Home > Article > Backend Development > Introduction to C++11 multi-threaded programming basics
1. Create a new thread in C 11
In every c application, there is a default main thread, which is the main function. In c 11, we can create it by Objects of class std::thread to create other threads. Each std::thread object can be associated with a thread by simply including the header file 61fe42cd48946e53c78c0e2bbfbc7b04. You can use a std::thread object to attach a callback that will be executed when this new thread starts. These callbacks can be function pointers, function objects, or Lambda functions.
Thread objects can be created via std::thread thObj(e713b189830c2ff991f906dd1cce83fe). The new thread will start immediately after the new object is created, and the passed callback will be executed in parallel with the started thread. Additionally, any thread can wait for another thread to exit by calling the join() function on that thread's object.
Create a thread using a function pointer:
//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; }
Create a thread using a function object:
#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::thread object has an associated id , std::thread::get_id() —-The id of the corresponding thread object is given in the member function;
std::this_thread::get_id()—-The id of the current thread is given, if std::thread object If there is no associated thread, get_id() will return the default constructed std::thread::id object: "not any thread", std::thread::id can also represent id.
Once a thread is started, another thread can wait for this thread to finish executing by calling the join() function on the std::thread object:
std::thread threadObj(funcPtr); threadObj.join();
For example, the main thread starts 10 threads. After starting, the main function waits for them to complete execution. After joining all threads, the main function continues to execute:
#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 can separate the thread from the thread object, so that The thread is executed as a background thread, and the current thread will not be blocked. However, it will no longer be able to contact the thread after detach. If the thread execution function uses temporary variables, problems may occur. The thread calls detach to run in the background, and the temporary variables may Has been destroyed, then the thread will access the variables that have been destroyed, and you need to call the std::detach() function in the std::thread object:
std::thread threadObj(funcPtr) threadObj.detach();
After calling detach(), the std::thread object is no longer Associated with the actual execution thread, be careful when calling detach() and join() on the thread handle.
Pass parameters to the thread's associated Object or function, simply pass arguments to the std::thread constructor, and by default all arguments will be copied to the new thread's internal storage.
Pass parameters to the 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; }
Pass a reference to the thread:
#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; }
The output result is:
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
Even if threadCallback accepts parameters as references, it does not change the value of x in main, in the thread reference Outside it is invisible. This is because x in the thread function threadCallback refers to a temporary value copied in the stack of the new thread, which can be modified using 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; }
The output result is:
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
Specify a pointer to a member function of a class as Thread function, pass the pointer to the member function as the callback function, and point the pointer to the object as the second parameter:
#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; }
In multi-threading Sharing data between programs is simple, but this data sharing within a program may cause problems, one of which is a race condition. When two or more threads perform a set of operations in parallel and access the same memory location, one or more of them will modify the data in the memory location, which may lead to some unexpected results. This is competition. condition. Race conditions are often harder to spot and reproduce because they don't always occur; they occur only when the relative order in which two or more threads perform operations leads to unexpected results.
For example, create 5 threads, these threads share an object of class Wallet, and use the addMoney() member function to add 100 yuan in parallel. So, if initially the money in the wallet is 0, then after all threads' competition execution is completed, the money in the wallet should be 500. However, since all threads modify the shared data at the same time, in some cases, the money in the wallet may Much less than 500.
The test is as follows:
#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; }
Each thread increases the same member variable "mMoney" in parallel, which seems to be a line, but this "nMoney" is actually converted into 3 machine commands:
·Load the "mMoney" variable in Register
·Increase the value of register
·Update the "mMoney" variable with the value of register
In this case, an increment will be ignored because it is not an increment mMoney variable, but a different register is added, and the value of the "mMoney" variable is overwritten.
为了处理多线程环境中的竞争条件,我们需要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; }
The above is the detailed content of Introduction to C++11 multi-threaded programming basics. For more information, please follow other related articles on the PHP Chinese website!