在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
C++11在标准库中为多线程提供组件, 使用线程需要包含头文件 thread, 其命名空间为 std. 启动新线程 每个进程至少有一个线程: 执行main()函数的线程, 其余线程有其各自的入口函数(线程函数)。 // 启动一个线程: void MyThread(const std::string& str) { PRINT_LINE_INFO(); std::cout << str << std::endl; } //std::thread t(MyThread, "Hello C..."); std::thread t([] { MyThread("Hello C..."); MyThread("Hello C2..."); }); // 对于类方法, 需要使用 std::bind. std::thread t(std::bind(&ThreadExample::MyThread, this, "msg")); ThreadGuard tg(t); 如果 std::thread 对象销毁之前还没有调用 join 或 detach, 程序就会终止( std::thread 的析构函数会调用 std::terminate() ). 因此, 即便是有异常存在, 也需要确保线程能够正确的加入(joined)或分离(detached). #ifndef _THREAD_GUARD_ #define _THREAD_GUARD_ #include <thread> class ThreadGuard { public: ThreadGuard(std::thread& t_) : t(t_){} ~ThreadGuard() { if (t.joinable()) { t.join(); } } ThreadGuard(const ThreadGuard &) = delete; ThreadGuard& operator=(const ThreadGuard &) = delete; private: std::thread& t; }; #endif // _THREAD_GUARD_ 如果是分离线程, 必须保证可访问数据的有效性, 否则会产生未定义的行为, 如同单线程中一个对象被销毁后再访问一样. class Func { int& i; public: Func(int& i_) : i(i_) {} void operator() () { for (unsigned j = 0; j < 10; ++j) { // 潜在访问隐患:悬空引用 i std::cout << i << " "; } std::cout << std::endl; } }; { // 某个作用域内 int* p = new int(100); Func f(*p); std::thread t(f); t.detach(); // 不等待线程结束 delete p; } // 新线程可能还在运行
线程函数线程函数可以有不同的参数, 向线程传递参数,只要在构造 std::thread 对象时,按照线程函数参数列表一一对应传入即可。线程函数有几点需要注意的地方: (1) 默认的参数会被拷贝到独立的线程中,即使是引用的形式, 如果需要需要传递引用, 需要使用 std::ref 显示说明(并且线程函数参数也需要声明为引用). void ThreadParamRef(std::string& str) { str += " --> add"; } void ThreadParam(std::string str) { str += " --> add"; } std::string str("Hello C++ Thread..."); //std::thread t(ThreadParamRef, str); std::thread t(ThreadParamRef, std::ref(str)); // 只有这种形式才能在线程执行完毕后输出 Hello C++ Thread... --> add //std::thread t(ThreadParam, std::ref(str)); t.join(); std::cout << str << std::endl; (2) 线程参数传递时需要注意不能传入局部变量, 考虑下面的代码,buffer②是一个指针变量,指向本地变量,然后本地变量通过buffer传递到新线程中②。 void f(int i,std::string const& s); void oops(int some_param) { char buffer[1024]; // 1 sprintf(buffer, "%i",some_param); std::thread t(f,3,buffer); // 2 t.detach(); } // 正确的方法 void f(int i,std::string const& s); void not_oops(int some_param) { char buffer[1024]; sprintf(buffer,"%i",some_param); std::thread t(f,3,std::string(buffer)); // 使用std::string,避免悬垂指针 t.detach(); } (3) 线程函数参数传递时, 可以移动, 但不能拷贝. "移动"是指: 原始对象中的数据转移给另一对象,而转移的这些数据在原始对象中不再保存. void ThreadParamUniquePtr(std::unique_ptr<int> up) { std::cout << (up.get() ? *up : -1) << std::endl; } std::thread t(ThreadParamUniquePtr, std::move(up)); //std::thread t(ThreadParamUniquePtr, up); // 不能编译 //std::thread t(ThreadParamUniquePtr, std::ref(up)); // 要求线程函数参数也为引用才能编译 t.join(); std::cout << (up.get() ? *up : -1) << std::endl; // 将输出-1
线程所有权转移线程是资源独占型, 但可以将所有权转移给别的对象. 如果一个 std::thread 对象与一个运行的线程关联, 此时接受一个新的线程所有权时, 其以前关联的线程将直接调用 std::terminate() 终止程序继续运行. std::thread t1(f); 线程对象也可以在函数中进行转移. std::thread f1() 由于 std::thread 是可转移的, 如果容器对移动操作支持, 则可以将 std::thread 对象放入其中. class Func { int i; public: Func(int i_) : i(i_) {} void operator() () { for (unsigned j = 0; j < 10; ++j) { std::cout << i << " "; } std::cout << std::endl; } }; std::vector<std::thread> threads; for (int i = 1; i < 10; i++) { Func f(i); //std::thread t(f); //v.push_back(t); // 不能采用这种方式 //v.push_back(std::move(t)); // 需要使用移动操作才可以 threads.push_back(std::thread(f)); } std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); // 对每个线程调用join()
常用函数 std::thread::hardware_concurrency() 返回 CPU 核心线程数. 如果无法查询系统信息时, 返回0. (static 函数)
一个更好的ThreadGuard #ifndef _THREAD_GUARD_ #define _THREAD_GUARD_ template <class _Thread> class ThreadGuard { public: explicit ThreadGuard(_Thread& t_) : t(t_) {} ~ThreadGuard() { if (t.joinable()) { t.join(); } } ThreadGuard(const ThreadGuard &) = delete; ThreadGuard& operator=(const ThreadGuard &) = delete; private: _Thread& t; }; #endif // _THREAD_GUARD_
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论