自汇合的多线程

C++11以来提供了C++原生的多线程std::thread,这极大的方便了多线程的书写。在此之前书写多线程时需要平台原生API,这对于跨平台尤其是跨多平台程序来讲,代码书写及维护都是极大的工作量。std::thread具有非常高的优势,但是其也有自己的缺点,以下代码为例,

cpp 复制代码
void using_thread_with_no_join()
{
	std::thread t{[](){
		std::cout<<"sub thread execate, thread id"<<std::this_thread::get_id();
	}};
}

运行如上代码时,会出现崩溃,堆栈信息如下, 由如上堆栈信息可知,崩溃原因为std::thread在析构时,如果对象仍为joinable状态,则会触发中断,为避免崩溃需要在std::thread析构器前需要将其置于非joinable状态,即需要主动调用join或detach接口。如果忘记了便会出现如上的崩溃。 既然已经有了RAII思想了,那必然是可以通过该思想来解决忘记join或detach导致崩溃的问题。所以std::jthread应运而生。当然std::jthread不止于此。

std::jthread

剖析其源码是了解其机理的最好方法,std::jthread的部分源码整理如下:

cpp 复制代码
#if _HAS_CXX20
class jthread {
public:
    jthread() noexcept : _Impl{}, _Ssource{nostopstate} {}

    template <class _Fn, class... _Args, enable_if_t<!is_same_v<remove_cvref_t<_Fn>, jthread>, int> = 0>
    _NODISCARD_CTOR explicit jthread(_Fn&& _Fx, _Args&&... _Ax) {
			//-------
    }

    ~jthread() {
        _Try_cancel_and_join();
    }

    jthread(const jthread&)     = delete;
    jthread(jthread&&) noexcept = default;
    jthread& operator=(const jthread&) = delete;

    jthread& operator=(jthread&& _Other) noexcept {
			//--------
    }

private:
    void _Try_cancel_and_join() noexcept {
        if (_Impl.joinable()) {
            _Ssource.request_stop();
            _Impl.join();
        }
    }

    thread _Impl;
    stop_source _Ssource;
};
#endif // _HAS_CXX20

由以上代码可知:

  1. 关注其构造函数:jthread不存在拷贝构造函数和拷贝赋值,存在移动构造函数和移动赋值运算符,即jthread不可拷贝但是可以转移。
  2. 关注其成员变量_Impl为std::thread类型,即std::jthread确系采用RAII思想,在构造函数内构造std::thread,但是在其析构函数内判断是否为joinable状态,若其为joinable状态则调用std::thread的join函数,致使std::thread在析构时恒为非joinable,不会触发崩溃。关于此部分功能不再赘述,完全为std::thread的套壳。
  3. 关注其成员变量_Ssource为std::stop_source类型,std::stop_source内维护stop_source的状态,其状态为std::_Stop_state,而std::_Stop_state实则是原子变量,通过判断该原子变量的值来处理线程的外部请求中断。

std::jthread使用实例

cpp 复制代码
	std::jthread j{[]{
		std::cout << "sub jthread execuate, thread id" << std::this_thread::get_id();
	}};

	//正常输出,并未崩溃,某次执行结果如下
	//sub jthread execuate, thread id35732

std::jthread处理外部请求中断

std::jthread提供三个接口并配合std::stop_token的stop_requested来实现外部请求中段处理。

cpp 复制代码
//std::jthread
_NODISCARD stop_source get_stop_source() noexcept {
        return _Ssource;
    }

 _NODISCARD stop_token get_stop_token() const noexcept {
     return _Ssource.get_token();
 }

 bool request_stop() noexcept {
     return _Ssource.request_stop();
 }

//stop token
 _NODISCARD bool stop_requested() const noexcept {
     const auto _Local = _State;
     return _Local != nullptr && _Local->_Stop_requested();
 }

处理外部请求中断的代码样例如下

cpp 复制代码
void using_jthread_with_stop_token()
{
	std::jthread j{ [](std::stop_token token) {
		std::cout << "sub jthread execate, thread id" << std::this_thread::get_id()<<"\n";
		for (int i =0; i< 20; i++)
		{
			std::cout<<"sub jthread "<<i<<"\n";
			std::this_thread::sleep_for(std::chrono::seconds(1));
			if (token.stop_requested())
			{
				std::cout<<"exit sub jthread " << std::this_thread::get_id() << "\n";
				return;
			}
		}
	} };

	std::cout << "running main thread "<<std::this_thread::get_id()<<"\n";
	std::this_thread::sleep_for(std::chrono::seconds(5));
	j.request_stop();
	std::cout << "exit main thread " << std::this_thread::get_id() << "\n";
}

//output result:
/*
running main thread 34396
sub jthread execate, thread id21536
sub jthread 0
sub jthread 1
sub jthread 2
sub jthread 3
sub jthread 4
exit main thread 34396
exit sub jthread 21536
*/

由源码可知,除直接使用std::jthread对象请求中断外,还可以使用source,即通过std::jthread的get_stop_source接口获得其source,而后通过source来请求中断,示例代码如下:

cpp 复制代码
void using_jthread_with_source_request_stop()
{
	std::jthread j{ [](std::stop_token token) {
		std::cout << "sub jthread execuate, thread id " << std::this_thread::get_id() << "\n";
		for (int i = 0; i < 20; i++)
		{
			std::cout << "sub jthread " << i << "\n";
			std::this_thread::sleep_for(std::chrono::seconds(1));
			if (token.stop_requested())
			{
				std::cout << "exit sub jthread " << std::this_thread::get_id() << "\n";
				return;
			}
		}
	} };

	
	auto source = j.get_stop_source();
	std::thread  t{[](std::stop_source source){
		std::cout << "running t thread " << std::this_thread::get_id() << "\n";
		std::this_thread::sleep_for(std::chrono::seconds(5));
		source.request_stop();
	
	},source};
	t.join();
	std::cout << "t thread  joined" << "\n";
}
//output result:
/*
running t thread 20280
sub jthread execuate, thread id 4164
sub jthread 0
sub jthread 1
sub jthread 2
sub jthread 3
sub jthread 4
t thread  joined
exit sub jthread 4164

*/

总结

  1. std::jthread析构自动汇合,不回崩溃;
  2. std::jthread支持joinable、join、detach、get_id、hardware_concurrency等原生std::thread的接口,故std::jthread可以无缝替换std::thread;
  3. std::jthread支持外部请求中断,无需再向使用std::thread那样,提供一个标志位来作为线程启停的标志

欢迎关注同名公众号【程序员的园】

相关推荐
慢慢向上的蜗牛26 分钟前
微软vcpkg包管理工具如何使用?
c++·microsoft·vcpkg·跨平台编译
wangjialelele36 分钟前
详解mysql命令行操作与语言链接
c语言·数据库·c++·mysql·oracle
江塘2 小时前
机器学习-决策树多种生成方法讲解及实战代码讲解(C++/Python实现)
c++·python·决策树·机器学习
初见无风2 小时前
4.4 Boost库工具类assign 的使用
开发语言·c++·boost
月夜的风吹雨2 小时前
【C++ STL容器适配器】:解密Stack、Queue与Priority Queue的设计智慧
开发语言·c++·stl·优先级队列··队列·适配器
二川bro2 小时前
第48节:WebAssembly加速与C++物理引擎编译
java·c++·wasm
2501_941111932 小时前
基于C++的区块链实现
开发语言·c++·算法
hetao17338372 小时前
2025-11-16~17 hetao1733837的刷题记录
c++·算法
_OP_CHEN2 小时前
算法基础篇:(九)贪心算法拓展之推公式:从排序规则到最优解的推导艺术
c++·算法·贪心算法·推公式·算法竞赛·acm/icpc
czxyvX2 小时前
010-C++之List
开发语言·c++·list