《C++并发编程实战》笔记(一、二)
一、简介
抽象损失:对于实现某个功能时,可以使用高级工具,也可以直接使用底层工具。这两种方式运行的开销差异称为抽象损失。
二、线程管控
2.1 线程的基本控制
1. 创建线程
线程相关的管理函数和类在头文件:
#include <thread>
创建一个线程使用如下方法:
std::thread t(callable);
callable
:线程函数,可以是任意的可调用对象- 线程对象创建后会立即启动线程运行
2. 控制线程的结束
线程启动后,必须显式指定线程结束的方式:
阻塞等待其结束(汇合),使用如下方法:
t.join();
- 只要调用了
join()
,主线程会阻塞等待该线程执行结束,join()
执行结束后,隶属于该线程的任何存储空间都会被清除,且线程对象不再关联到结束的线程 - 成员函数
t.joinable()
返回线程对象t
是否关联到某个线程,当join()
执行结束后t.joinable()
会返回false
- 只有关联到某个线程的对象才能调用
join()
让线程后台运行(分离),使用如下方法:
t.detach();
- 线程对象必须关联某个线程(
joinable()
为true
时)才能调用detach
,调用后线程的归属权和控制权都转移给C++运行时库,它独立于主线程继续运行,直至线程函数运行结束,且C++运行时库会保证线程退出后与之关联的资源都被正确回收 - 被
detach
的线程不再关联实际的执行线程(joinable()
会返回false
),所以不可再调用join()
注意:如果线程对象销毁时都没有指定结束方式,则std::thread
的析构函数会调用std::terminate()
终止整个程序
3. 异常时保证线程正常结束
创建线程对象后,如果在指定线程结束方式前,因为执行其他代码造成了异常,可能会导致指定线程结束方式的代码被略过,从而导致程序终止。即:
void func() {
try {
// 线程 t 去执行 do_something() 函数
std::thread t(do_something);
// 此时如果发生异常等,下面的join可能无法被调用,退出func时程序会被中断
do_other_thing();
} catch (...) {
throw;
}
t.join();
return;
}
解决方法:
- 一种解决方法是保证在程序的所有执行路径都会指定线程结束方式
void func() { try { std::thread t(do_something); do_other_thing(); } catch (...) { // 保证一定会指定线程结束方式 t.join(); throw; } t.join(); return; }
- 使用RAII方法,利用对象管理线程,在构造函数中创建线程,在析构函数中指定线程的结束方式
class RaiiThreadGuard { public: explicit RaiiThreadGuard(std::thread &t) : t_(t) {} ~RaiiThreadGuard() { // 类对象离开作用域时一定会调用析构,保证一定会指定线程对象的结束方式 t_.join(); } // 将拷贝构造和拷贝赋值都定义为删除的,避免编译器优化造成重复调用析构 RaiiThreadGuard(const RaiiThreadGuard &) = delete; RaiiThreadGuard& operator=(const RaiiThreadGuard &) = delete; private: std::thread& t_; }; // main 函数中通过如下方式使用 int main() { // 创建线程 std::thread t(HelloFunction); // 使用 RAII 保证线程对象一定会调用 join RaiiThreadGuard thread_guard(t); // 即使后续再执行其他代码造成退出作用域,编译器会保证执行 thread_guard 的析构指定线程的结束方式 return 0; }
2.2 向线程函数传递参数
线程函数所需要的参数可以直接紧跟在std::thread
的线程函数实参后:
- 线程具有内部存储空间,线程函数的实参会先使用拷贝构造函数,将
std::thread
的实参复制到创建的线程;在创建好的线程中,新复制出来的实参被当成临时量,以右值形式传递给新线程中的线程函数 - 根据参数的传递过程,如果线程函数包含非const引用形参,为避免在线程内执行时收到右值,需要通过
std::ref
在std::thread
传递实参(与std::bind
函数的使用相同) - 类的非静态成员函数第一个参数是指向对象的隐式
this
指针,如果想在线程中执行某个成员函数,需要将对象地址传递给成员函数的隐式this
指针
class TestClass {
public:
/** @brief 默认构造函数 */
TestClass(){
std::cout << "TestClass default constructor." << std::endl;
std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;
}
/** @brief 拷贝构造函数 */
TestClass(const TestClass& ohter) {
std::cout << "TestClass copy constructor." << std::endl;
std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;
}
/** @brief 移动构造函数 */
TestClass(TestClass&& ohter) {
std::cout << "TestClass move constructor." << std::endl;
std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;
}
/** @brief 类的内部函数 */
void InnerFunction() {
std::cout << "TestClass Inner Function." << std::endl;
std::cout << "std::thread::id: " << std::this_thread::get_id() << std::endl;
}
};
void func(int num, std::string &str, TestClass obj) {
str += std::to_string(num);
}
// main 函数
std::string str("The num: ");
TestClass test_class;
// 参数直接作为 std::thread 的后续参数传入
// 1. 对象会先调用拷贝构造复制到线程,再通过std::move()以右值复制给线程内的函数形参
// 2. 线程函数的引用形参要通过 std::ref() 传递
std::thread t(func, 3, std::ref(str), test_class);
t.join();
std::cout << str << std::endl;
// 在线程中运行类的成员函数,需要将对象的地址传递给成员函数的隐式this指针
std::thread t2(&TestClass::InnerFunction, &test_class);
t2.join();
/* 输出
TestClass default constructor.
std::thread::id: 140737348195264
TestClass copy constructor.
std::thread::id: 140737348195264
TestClass move constructor.
std::thread::id: 140737348179520
The num: 3
TestClass Inner Function.
std::thread::id: 140737348179520
*/
2.3 转移线程归属权
在某些情况下,如想要指定特定的函数等待线程结束,可能需要转移线程的归属权。
std::thread
与std::unique
类似,由于独占资源,其对象不能被复制,只支持移动:
std::thread t1(some_function);
std::thread t2 = std::move(t1);
2.4 运行时确定线程数量
标准库的静态函数std::thread::hardware_concurrency()
函数返回程序在执行时可以真正并发的线程数量:
- 若信息无法获取,返回0
- 否则返回支持并发的线程数
2.5 标识不同线程
每个线程都有一个唯一的ID,该ID是一个std::thread::id
类型的变量:
- 可以使用线程对象的
std::thread::get_id()
函数返回线程对象的ID - 在程序中可以使用
std::this_thread::get_id()
函数获取运行当前程序的线程ID - 默认构造创建的
std::thread::id
类型变量表示线程不存在
std::thread::joinable()
函数会利用线程对象的ID确定返回值,即:
- 若
this.get_id() != std::thread::id()
则返回 true(判断当前线程ID和默认构造的线程ID类型变量是否相同) - 否则返回false(表示线程对象没有关联到任何线程,线程不存在)
原文地址:https://blog.csdn.net/qq_36793268/article/details/140388009
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!