一家专门做软件的网站,拜师做网站,手机能搭建网站吗,wordpress文字加边框一、简介
抽象损失#xff1a;对于实现某个功能时#xff0c;可以使用高级工具#xff0c;也可以直接使用底层工具。这两种方式运行的开销差异称为抽象损失。
二、线程管控
2.1 线程的基本控制
1. 创建线程
线程相关的管理函数和类在头文件#xff1a;
#include …一、简介
抽象损失对于实现某个功能时可以使用高级工具也可以直接使用底层工具。这两种方式运行的开销差异称为抽象损失。
二、线程管控
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 保证线程对象一定会调用 joinRaiiThreadGuard 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表示线程对象没有关联到任何线程线程不存在