thread类解析
构造函数
thread()
无参构造,会创建一个空的线程对象。
thread(FunctionCallback, ...Args)
创建并开启一个线程,线程任务就是参数里的回调函数。
thread(thread&& other)
移动构造,具体请参照C++的移动语义。
PS:
thread类没有拷贝构造。
thread(const thread&) = delete;
thread& operator=(const thread&) = delete;
成员函数
join函数解释
阻塞调用线程的,在一个线程环境调用,那么这个线程环境将会等待join()返回后才继续往下执行
如代码1.1,输出结果:
Main: 18972
子线程开始:10188
i=0 i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8 i=9
Main: 18972 // 只一句话无论执行该程序多少次,永远会在t1线程任务完成后才会输出
代码清单1.1
#include <thread>
#include <iostream>
using namespace std;
void test1()
{
cout << "子线程开始:" << this_thread::get_id() << endl;
for (int i = 0; i < 10; i++)
{
cout << " i=" << i;
}
}
int main()
{
cout<< "\r\nMain: " << this_thread::get_id() << endl;
thread t1{test1};
// 把t1 加入main线程,main线程会等待t1执行完成后才会向下执行
t1.join();
cout << "\r\nMain: " << this_thread::get_id() << endl;
return 0;
}
修改代码清单1.1 的部分内容,进一步体会join()。
void test1()
{
cout << "子线程开始:" << this_thread::get_id() << endl;
for (int i = 0; i < 10; i++)
{
this_thread::sleep_for(chrono::seconds(1));
cout << " i=" << i;
}
}
int main()
{
cout<< "\r\nMain: " << this_thread::get_id() << endl;
thread t1{test1}; // 创建线程,并开始执行
// 该部分代码会和 t1 线程并发执行
for (int i = 0; i < 10; i++)
{
this_thread::sleep_for(chrono::seconds(1));
cout << " j=" << i;
}
// 把t1 加入main线程,main线程会等待t1执行完成
t1.join();
// 该部分代码会等待t1线程执行完后才执行
for (int i = 0; i < 10; i++)
{
this_thread::sleep_for(chrono::seconds(1));
cout << " k=" << i;
}
cout << "\r\nMain: " << this_thread::get_id() << endl;
return 0;
}
PS: 如果打算等待对应线程,则需要细心挑选调用join()的位置。
detach函数解释
情景搭建:有这么一个情况,用户关闭了应用程序,但是用户想要下载并不停止。
分析:总所周知,关闭一个程序后操作系统会回收分配的资源,包括给程序分配的变量。如果用户关闭了程序,但是你的下载线程并没有结束,这个时候就会报错。如图:
该报错是由代码清单1.2引起的。
代码清单1.2
#include <thread>
#include <iostream>
using namespace std;
void test1()
{
// 至少会执行 100s
cout << "子线程开始:" << this_thread::get_id() << endl;
for (int i = 0; i < 100; i++)
{
this_thread::sleep_for(chrono::seconds(1));
cout << " i=" << i << endl;
}
}
int main()
{
// 一个代码块,程序出这个代码块后,t1会被释放
{
thread t1{ test1 };
}
for (int i = 0; i < 10; i++)
{
this_thread::sleep_for(chrono::seconds(2));
cout << "do something " << i << endl;
}
return 0;
}
代码清单1.2里,子线程对象很快就被释放了,但是子线程还在执行,所以就会报错。
为了解决这个问题,我们可以使用join让主线程等待子线程,但有一点不好,主线程会阻塞在join这里,我们应该让主线程去干自己的事。
第二种方法就是,不让子线程依赖该子线程对象 -- detach
使用该方法后,线程就不在依赖线程对象,哪怕线程对象被销毁,该线程依旧会执行。如代码清单 1.2.1 在代码块结束后 t1线程对象被自动回收,但该线程并没有结束,会一直运行,直到主线程结束。
代码清单 1.2.1
#include <thread>
#include <iostream>
using namespace std;
void test1()
{
// 至少会执行 100s
cout << "子线程开始:" << this_thread::get_id() << endl;
for (int i = 0; i < 100; i++)
{
this_thread::sleep_for(chrono::seconds(1));
cout << " i=" << i << endl;
}
}
int main()
{
// 一个代码块,程序出这个代码块后,t1会被释放
{
thread t1{ test1 };
t1.detach();
}
for (int i = 0; i < 10; i++)
{
this_thread::sleep_for(chrono::seconds(2));
cout << "do something " << i << endl;
}
return 0;
}
thread类源码解读
以下代码清单是一个简化并写有注释的thread类。
class thread
{ // class for observing and managing threads
public:
class id; // 线程ID
using native_handle_type = void*; // 回调函数句柄
// 无参构造,空的线程对象
thread() noexcept : _Thr{} {}
private:
// 创建并执行线程
template <class _Fn, class... _Args>
void _Start(_Fn&& _Fx, _Args&&... _Ax) {
using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>;
auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{});
_Thr._Hnd =
reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
if (_Thr._Hnd) { // ownership transferred to the thread
(void) _Decay_copied.release();
} else { // failed to start thread
_Thr._Id = 0;
_Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN);
}
}
public:
// 使用函数指针作为参数的构造函数
template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
_NODISCARD_CTOR_THREAD explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); // 调用start 创建并执行线程
}
~thread() noexcept {
if (joinable()) {
_STD terminate();
}
}
// 移动构造
thread(thread&& _Other) noexcept : _Thr(_STD exchange(_Other._Thr, {})) {}
// 移动赋值
thread& operator=(thread&& _Other) noexcept {
if (joinable()) {
_STD terminate();
}
_Thr = _STD exchange(_Other._Thr, {});
return *this;
}
// 不允许拷贝构造 和 拷贝赋值
thread(const thread&) = delete;
thread& operator=(const thread&) = delete;
// 交换两个线程对象
void swap(thread& _Other) noexcept {
_STD swap(_Thr, _Other._Thr);
}
// 判断线程是否有效, 是否可以执行
_NODISCARD bool joinable() const noexcept {
return _Thr._Id != 0;
}
// 阻塞调用线程的,在一个线程环境调用,那么这个线程环境将会等待join()返回后才继续往下执行
void join() {
if (!joinable()) {
_Throw_Cpp_error(_INVALID_ARGUMENT);
}
if (_Thr._Id == _Thrd_id()) {
_Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR);
}
if (_Thrd_join(_Thr, nullptr) != _Thrd_success) {
_Throw_Cpp_error(_NO_SUCH_PROCESS);
}
_Thr = {};
}
// 分离对象和线程的依赖
void detach() {
if (!joinable()) {
_Throw_Cpp_error(_INVALID_ARGUMENT);
}
_Check_C_return(_Thrd_detach(_Thr));
_Thr = {};
}
// 获取线程的ID
_NODISCARD id get_id() const noexcept;
// 获取线程任务句柄
_NODISCARD native_handle_type native_handle() noexcept /* strengthened */ { // return Win32 HANDLE as void *
return _Thr._Hnd;
}
private:
_Thrd_t _Thr; // 线程对象维护的成员变量
};
思考:
- 为什么创建线程对象的时候,传入函数指针,线程就自动执行
答:该构造函数的原形是:
// 使用函数指针作为参数
explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
}
会自动调用start函数,start函数是私有成员函数,start函数会创建线程并执行,_Start函数里的主要代码:
_Thr._Hnd = reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id));
标签:STD,thread,void,Thr,C++,源码,线程,._ From: https://www.cnblogs.com/muzhipin/p/17294309.html