首页 > 编程语言 >C++ thread 源码阅读笔记

C++ thread 源码阅读笔记

时间:2023-04-06 21:46:14浏览次数:49  
标签:STD thread void Thr C++ 源码 线程 ._

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函数解释

情景搭建:有这么一个情况,用户关闭了应用程序,但是用户想要下载并不停止。
分析:总所周知,关闭一个程序后操作系统会回收分配的资源,包括给程序分配的变量。如果用户关闭了程序,但是你的下载线程并没有结束,这个时候就会报错。如图:
image

该报错是由代码清单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; // 线程对象维护的成员变量
};
思考:
  1. 为什么创建线程对象的时候,传入函数指针,线程就自动执行

答:该构造函数的原形是:

// 使用函数指针作为参数
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

相关文章

  • 源码安装slurm
    一、源码安装munge1、下载munge下载地址:https://github.com/dun/munge/releases2、安装编译 tar-Jxvfmunge-0.5.15.tar.xz./bootstrap./configure--prefix=/usr/local/munge\--sysconfdir=/usr/local/munge/etc\--localstatedir=/usr/local/munge/local\--with-......
  • flask-day4——pipreqs模块、函数和方法的区别、threading.local对象、偏函数、flask
    目录一、请求上下文分析(源码:request原理)1.1导出项目的依赖(pipreqs模块)1.2函数和方法1.3threading.local对象代码演示自定义封装local,实现兼容线程和协程1.4偏函数1.5flask整个生命执行流程(1.1.4版本为例)二、wtforms(了解)三、作业1、为什么有了gil锁还要互斥锁2、什么是进程,线......
  • C++运算符重载
    什么是运算符重载运算符重载(Operatoroverload)是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时做出不同的行为。运算符重载的意义运算符重载的本质是函数重载,是实现多态的重要手段,为用户提供了一个直观的接口。调用运算符操作自定义数据类型其实就是调用......
  • c++primer3.5数组
    3.5 数组数组大小固定,灵活性差。3.5.1 定义和初始化内置数组数组维度是一个常量表达式,数组的元素是一个对象。不允许用auto关键字由初始值的列表推断类型,必须指定数组类型。数组无法进行拷贝。数组本身就是对象,允许定义数组的指针以及数......
  • 请求上下文分析、函数和方法、threading.local对象、偏函数、flask整个生命执行流程(1
    请求上下文分析(源码:request原理)导出项目的依赖#之前pipfreeze>requirments.txt把当前解释器环境下的所有第三方依赖都导出来#使用第三方模块,更精确的导出依赖pipreqs第一步:安装pip3installpipreqs第二步:使用命令,导出项目依赖pipreqs./w......
  • 【flask】flask请求上下文分析 threading.local对象 偏函数 flask1.1.4生命执行流程
    目录上节回顾今日内容1请求上下文分析(源码:request原理)1.1导出项目的依赖1.2函数和方法1.3threading.local对象1.4偏函数1.5flask整个生命执行流程(1.1.4版本为例)2wtforms(了解)补充上节回顾#1蓝图 -第一步:导入-第二步:实例化得到对象,可以指定static和templates......
  • flask之request源码和第三方模块wtforms
    目录请求上下文分析(源码:request原理)1.导出项目的依赖2.函数和方法3.threading下的local对象4.偏函数5.flask整个生命执行流程---flask1.1.41版本为例wtforms---了解请求上下文分析(源码:request原理)1.导出项目的依赖以前导出项目的依赖:pipfreeze>requirements.txt......
  • c++字符串拆分
    1staticvoidSplitString(conststring&data,conststring&delim,2std::vector<string>*result){3std::string::size_typepos;4constintsize=data.size();56for(intindex=0;index<size;++index)......
  • flask源码分析
    目录请求上下文分析(源码:request原理)导出项目的依赖函数和方法threading.local对象偏函数flask整个生命执行流程(1.1.4版本为例)wtforms请求上下文分析(源码:request原理)导出项目的依赖之前的pipfreeze>requeirments.txt会把当前解释器环境下的所有第三方依赖都导出来......
  • 六轴桌面机械臂 上位机(PC)源码与下位机(单片机)源码
    六轴桌面机械臂上位机(PC)源码与下位机(单片机)源码YID:1690609972944148......