当前位置: 首页 > news >正文

C++ 由 std::thread 初始化想到的

我在写一个多线程的网络服务器,想在主线程之外创建一些线程执行特定的逻辑。我还想把创建线程的代码、线程执行的逻辑、线程自己拥有的或者和主线程共享的资源都封装在一个类里。在具体实现时,我遇到了语言层面的问题。

题外话:把线程相关的逻辑和资源封装在一个类里是个不错的设计,这样就把线程可能访问的资源和只有主线程会访问的资源在代码上清晰的区分开了。还可以把主线程访问线程资源的操作封装在类的方法里,这样访问线程资源的操作就也和访问一般资源的操作区分开了。

以下是我用于封装线程的类的声明

class worker {    
public:worker();void add_client(int fd_sock);private:void run();private:std::thread m_thread;int m_id;
};

我遇到的问题是 1. 如何初始化 m_thread。2. 如何在 m_thread 里跑 run(),而且是 m_thread 所在的那个对象的 run(),不是其他对象的 run(),如何用 cpp 表达这种逻辑。

关于初始化 m_thread

网上的资料都会交你怎么初始化 thread,并在 thread 里跑一些函数,大概是这样的

std::thread t2(f1, n + 1); // pass by value

但我现在遇到的情况是,定义 m_thread 和初始化 m_thread 是分开的,上述调用构造函数的语法就没法套用了。其实不仅仅是初始化 thread,初始化其他的作为类的成员存在的类对象都会有这个问题:在类声明里定义了这个成员对象,却只能在类的构造函数里初始化它。

这里就要用到调用构造函数的另一种语法:类名()

比如上述场景下,我可以在 worker 的构造函数里这么写(... 中的内容稍后揭秘)。

worker::worker() {m_thread = std::thread(...);
}

这里其实是构造了一个匿名的临时变量,再移动或者复制赋值给 m_thread。这种构造匿名临时对象的方式也适用于构造参数或者其他不想起名字的情况。

优化

上述方法是正确的,但先构造一个临时对象,再赋值给我们真正想初始化的对象岂不是很麻烦?所以 C++ 专门为这种问题提供了一种语法:成员初始化列表。

worker::worker() : m_thread(...) {...
}

这样就可以“直接初始化” m_thread,与 m_thread 作为普通变量时类似。

补充

我在不熟悉 C++ 时经常写出两种糊涂的初始化的代码。

// 糊涂写法,这个语法其实是函数声明
A a();   // 改正为 A a;// 糊涂写法,正确但是多此一举
A a = A(xxx); // 改正为 A a(xxx);

关于如何在 m_thread 里跑特定对象的 run

这里要提到一个概念叫成员函数指针。(这个概念我居然今天才知道)

成员函数指针的类型是这样的

// 返回值类型 (类名::*) (参数列表)
// 一般的函数指针是没有 “类名::” 这一部分的
// 以 run 为例
void (worker::*) ()// 对于一般的函数指针,使用方式如下
f(args)// 对于成员函数指针,使用时需要给出调用他的对象
// 同时也指明了这个成员函数是在哪个对象上下文中执行的
A.*f(args)
A_ptr->*f(args)

获取成员函数指针的方式很固定:&类名::成员函数名,比如 &worker::run

回到本篇设定的场景,thread 的构造函数和成员函数指针类型具有独特的配合,cppreference 中就给出了例子。其中 &foo::bar 是成员函数指针,&f 是用于调用成员函数的对象。此时 &f 不再是前面函数的参数,在 thread 里会被以 (&f)->*bar 这种方式使用。

std::thread t5(&foo::bar, &f);

利用 thread 构造函数的这种特性,我可以在 worker 的构造函数里这么写,使得构造出来的 m_thread 运行的是当前 worker 的 run 函数。下面的代码中还给出了其他不同写法(包括有关 lambda 而无关成员函数指针的特殊写法,不再赘述)

// 1. 成员初始化列表的初始化方式
worker::worker() : m_thread(&worker::run, this) {static int id = 0;m_id = ++id;// 2. 构造临时匿名对象再赋值// 这种写法是正确的,&worker::run 这种语法【&类名::方法名】取出成员函数指针// thread 构造函数能判断第一个参数是成员函数指针,此时第二个参数作为调用成员函数的对象,而非一般的函数参数// m_thread = std::thread(&worker::run, this);// 3. // 这种写法也是正确的,绕开了成员函数指针的问题// m_thread = std::thread([this] () {//     this->run();// });// 4. // 这种写法也是正确的,怎么知道 run 是成员函数的?// 捕获 this 的 lambda 比较特殊,捕获了 this 的 lambda 内部就仿佛类的一般的成员函数内部一样// 可以直接访问类的(包括私有的)成员和函数// m_thread = std::thread([this] () {//     run();// });
}

http://www.dtcms.com/a/354200.html

相关文章:

  • TencentOS Server 4.4 下创建mysql容器无法正常运行的问题
  • wireshark解析FLV插件分享
  • 嵌入式Linux(Exynos 4412)笔记
  • 3459. 最长 V 形对角线段的长度
  • 设计模式理解
  • Nishang PowerShell工具:原理详解+使用方法+渗透实战
  • Go+Gdal 完成高性能GIS数据空间分析
  • 深度学习:常用的损失函数的使用
  • “java简单吗?”Java的“简单”与PHP的挑战:编程语言哲学-优雅草卓伊凡
  • 白话FNN、RNN、Attention和self-attention等
  • 《从有限元到深度学习:我的金属疲劳研究进阶之路》
  • 反内卷加速全产业链价值重塑 通威股份等行业龙头或率先受益
  • 基于 C# OpenCVSharp 的模板匹配检测技术方案
  • 计算机日常答疑,一起寻找问题的最优解
  • select
  • SM4加密算法
  • Karatsuba
  • 前端工程化与AI融合:构建智能化开发体系
  • 4-4.Python 数据容器 - 字典 dict(字典 dict 概述、字典的定义与调用、字典的遍历、字典的常用方法)
  • CPU 虚拟化之Cpu Models
  • 代码随想录刷题Day43
  • 时间轮定时器HashedWheelTimer
  • WSL设置静态IP
  • window程序打包
  • Libvio网站与客户端访问故障排查指南(专业版)
  • 什么是低空经济?
  • JMeter 5.3 性能测试:文件下载脚本编写与导出文件接收完整指南
  • QT鼠标事件中的QMouseEvent :e
  • 深度学习---卷积神经网络CNN
  • PLC_博图系列☞基本指令”S_ODT:分配接通延时定时器参数并启动“