C++ 多线程std::thread以及条件变量和互斥量的使用
前言
- 本文章主要介绍C++11语法中std::thread的使用,以及条件变量和互斥量的使用。
std::thread介绍
构造函数
- std::thread 有4个构造函数
-
// 默认构造函,构造一个线程对象,在这个线程中不执行任何处理动作 thread() noexcept; // 移动构造函数。将 other 的线程所有权转移给新的thread 对象。之后 other 不再表示执行线程。 // 线程对象只可移动,不可复制 thread( thread&& other ) noexcept; // 创建线程对象,并在该线程中执行函数f中的业务逻辑,args是要传递给函数f的参数 template< class F, class... Args > explicit thread( F&& f, Args&&... args ); // 使用=delete显示删除拷贝构造, 不允许线程对象之间的拷贝 thread( const thread& ) = delete;
- 通过以下代码演示下如何构造函数的使用
-
#include <iostream> #include <thread> #include <chrono> void threadFunc2() { std::cout << "enter threadFunc2" << std::endl; } void threadFunc3(int data) { std::cout << "enter threadFunc3, data: " << data << std::endl; } class CThread4 { public: void threadFunc4(const char * data) { std::cout << "enter threadFunc4, data: " << data << std::endl; } }; void threadFunc5() { for (int i = 0; i < 5; i++) { std::cout << "enter threadFunc5" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } int main() { // 默认构造 std::thread th1; // 线程中执行函数 std::thread th2(threadFunc2); th2.join(); // 线程中执行带参函数 std::thread th3(threadFunc3, 10010); th3.join(); CThread4 ct4; // 线程中执行类成员函数 std::thread th4(&CThread4::threadFunc4, &ct4, "hello world"); th4.join(); std::thread th5_1(threadFunc5); // 使用移动构造 std::thread th5_2(std::move(th5_1)); th5_2.join(); // 执行lambda表达式 std::thread th6([] { std::cout << "enter threadFunc6" << std::endl; }); th6.join(); system("pause"); return 0; }
-
- 执行结果
-
enter threadFunc2 enter threadFunc3, data: 10010 enter threadFunc4, data: hello world enter threadFunc5 enter threadFunc5 enter threadFunc5 enter threadFunc5 enter threadFunc5 enter threadFunc6 请按任意键继续. . .
-
成员函数
-
// 获取线程ID std::thread::id get_id() const noexcept; // 阻塞当前线程,直至调用join的子线程运行结束 void join(); // 将执行线程从线程对象中分离,允许独立执行。 void detach(); // 判断主线程和子线程的关联状态 bool joinable() const noexcept; // 如果 *this 仍然有一个关联的运行中的线程,则调用 std::terminate()。 // 否则,将 other 的状态赋给 *this 并将 other 设置为默认构造的状态。 thread& operator=( thread&& other ) noexcept;
- 通过代码看下如何使用成员函数
-
#include <iostream> #include <thread> #include <chrono> void threadFunc3(int data) { std::cout << "enter threadFunc3, data: " << data << std::endl; } void threadFunc4(int data) { std::cout << "start threadFunc4, data: " << data << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "end threadFunc4, data: " << data << std::endl; } void threadFunc5(int data) { std::cout << "start threadFunc5, data: " << data << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "end threadFunc5, data: " << data << std::endl; } int main() { { // 线程中执行带参函数 std::thread th3(threadFunc3, 10010); std::cout << "th3 id: " << th3.get_id() << std::endl; // 此刻th3线程与主线程有关联 std::cout << "th3 joinable: " << th3.joinable() << std::endl; th3.join(); std::cout << "th3 id: " << th3.get_id() << std::endl; // 线程执行结束,此刻th3线程与主线程无关联 std::cout << "th3 joinable: " << th3.joinable() << std::endl; } { std::thread th5(threadFunc5, 10050); // 如果不想在主线程中等待子线程,可以使用detach。 // 这样即便主线程运行结束,子线程依旧会执行 // 实际使用时不建议这样做 th5.detach(); } system("pause"); return 0; }
-
- 执行结果
-
enter threadFunc3, data: 10010th3 id: 12820 th3 joinable: 1 th3 id: 0 th3 joinable: 0 start threadFunc5, data: 10050 请按任意键继续. . . end threadFunc5, data: 10050
-
条件变量
- 条件变量是C++11提供的一种用于等待的同步机制,它能阻塞一个或多个线程,直到收到另外一个线程发出的通知或者超时时,才会唤醒当前阻塞的线程。
- C++11中的条件变量叫
condition_variable
,需要配合std::unique_lock<std::mutex>
使用。 - 先看以下一段代码
-
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <condition_variable> int g_cnt = 0; // 定义互斥量 std::mutex g_mutex; // 定义条件变量 std::condition_variable g_cond; void threadFunc1() { while (g_cnt != 50) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } std::cout << "threadFunc1 g_cnt: " << g_cnt << std::endl; } void threadFunc2() { while (g_cnt < 100) { g_cnt++; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } int main() { { std::thread th1(threadFunc1); std::thread th2(threadFunc2); th1.join(); th2.join(); std::cout << "g_cnt: " << g_cnt << std::endl; } system("pause"); return 0; }
-
- 线程2对
g_cnt
进行递增操作,线程1在g_cnt
等于50时退出循环并打印结果。但这个流程有一个问题,比如线程1某次访问g_cnt时,值为49,下一次再访问时,值可能为50,也可能为51。如果g_cnt
值为51,那这个条件永远都不会满足,循环也就永远无法结束。并且线程1中,我们只想获取g_cnt
等于50这个状态,没必要每次都去访问,这也会耗费系统资源。 - 使用条件变量做以下修改
-
#include <iostream> #include <thread> #include <chrono> #include <mutex> #include <condition_variable> int g_cnt = 0; // 定义互斥量 std::mutex g_mutex; // 定义条件变量 std::condition_variable g_cond; bool g_flag = false; void threadFunc1() { std::unique_lock<std::mutex> lock(g_mutex); while (!g_flag) { // 阻塞等待,等待被唤醒 g_cond.wait(lock); } std::cout << "threadFunc1 g_cnt: " << g_cnt << std::endl; } void threadFunc2() { while (g_cnt < 100) { g_cnt++; if (g_cnt == 50) { g_flag = true; // 唤醒阻塞的线程 g_cond.notify_one(); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } int main() { { std::thread th1(threadFunc1); std::thread th2(threadFunc2); th1.join(); th2.join(); std::cout << "g_cnt: " << g_cnt << std::endl; } system("pause"); return 0; }
-
- 这样就可以保证线程1的条件肯定可以满足。
线程互斥
- 控制线程对共享资源的访问。比如写文件时,不能读文件,读文件时,不能写文件。
- C++11提供了4种互斥锁
std::mutex
:独占的互斥锁,不能递归使用。std::timed_mutex
:带超时的独占互斥锁,不能递归使用。在获取互斥锁资源时增加了超时等待功能。std::recursive_mutex
:递归互斥锁,不带超时功能。允许同一线程多次获得互斥锁。std::recursive_timed_mutex
:带超时的递归互斥锁。
- 分析以下这段代码的输出结果
-
#include <iostream> #include <thread> #include <chrono> #include <mutex> int g_cnt = 0; void threadFunc(int num) { for (int i = 0; i < num; i++) { g_cnt++; std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } int main() { { // 线程中执行带参函数 std::thread th1(threadFunc, 100); std::thread th2(threadFunc, 100); th1.join(); th2.join(); std::cout << "g_cnt: " << g_cnt << std::endl; } system("pause"); return 0; }
-
- 我们期望的
g_cnt
输出结果为200
,但实际上g_cnt
很大概率不是200
而是小于200
。这是由于没有对共享资源g_cnt
进行加锁保护,这会导致数据竞争。两个线程可能同时访问g_cnt
,导致某个线程的++
操作被另一个线程覆盖。 - 对上面代码做下修改,对共享资源
g_cnt
进行加锁保护。-
#include <iostream> #include <thread> #include <chrono> #include <mutex> int g_cnt = 0; // 定义互斥量 std::mutex g_mutex; void threadFunc(int num) { for (int i = 0; i < num; i++) { // 加锁 g_mutex.lock(); g_cnt++; // 解锁 g_mutex.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } int main() { { // 线程中执行带参函数 std::thread th1(threadFunc, 100); std::thread th2(threadFunc, 100); th1.join(); th2.join(); std::cout << "g_cnt: " << g_cnt << std::endl; } system("pause"); return 0; }
-
- 加锁后,就可以保证
g_cnt
的结果为200。 - 上面代码有这样一种风险。如果加锁后,中途退出而忘记解锁,就会导致死锁现象。
-
void threadFunc(int num) { for (int i = 0; i < num; i++) { // 加锁 g_mutex.lock(); g_cnt++; if (i == 50) { break; } // 解锁 g_mutex.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }
-
- C++ 11 提供了一种模板类
std::lock_guard
,可以简化互斥锁的写法。调用构造时加锁,离开作用域时解锁。不用手动加解锁,大大提高了安全性。 - 实现代码如下。即便中途退出,也不会出现死锁现象。
-
void threadFunc(int num) { for (int i = 0; i < num; i++) { // 加锁 std::lock_guard<std::mutex> lock(g_mutex); g_cnt++; if (i == 50) { break; } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } }
-
参考
- https://en.cppreference.com/w/cpp/thread/thread
原文地址:https://blog.csdn.net/new9232/article/details/143812986
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!