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

Linux OS:线程封装 | RAII封装锁 | 随机数运算任务封装

Linux OS:线程封装 | RAII封装锁 | 随机数运算任务封装

  • 一、Linux OS:线程封装
    • 1.1 线程私有成员
    • 1.2 线程成员函数
      • 1)构造函数初始化相关数据
      • 2)启动线程接口
      • 3)线程等待
      • 4)获取线程名
    • 1.3 总体代码
  • 二、RAII封装锁
  • 三、随机数运算任务疯转

一、Linux OS:线程封装

1.1 线程私有成员

 现在我们手动封装一个线程,其中类成员变量包含:线程id、线程名、线程运行状态(是否允许)、线程数据、创建线程的回调方法!具体如下:

template <class T>
using func_t = std::function<void(T)>; //回调方法重命名template <class T>
class Thread
{
private:pthread_t _tid; // 线程idstd::string _threadname; // 线程名bool _isrunning; // 线程运行状态(是否允许)func_t<T> _func; // 创建出的新线程执行该回调函数T _data; // 线程数据
};

1.2 线程成员函数

 对于线程我们将实现下面几个成员方面:构造函数初始化相关数据(新创建的线程没有启动运行)、启动线程接口、线程等待接口、获取线程名!

1)构造函数初始化相关数据

 对于构造函数,我们通过外部用户传入线程名、执行方法、线程数据!

Thread(const std::string& threadname, func_t<T> func, T data):_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data){}

2)启动线程接口

 线程默认是没有运行的,我们通过在启动线程接口调用pthread_create函数来创建线程,通过构造函数获取到的数据,将相关数据传递给pthread_create函数。

static void *ThreadRoution(void *args)
{Thread *ts = static_cast<Thread *>(args);ts->_func(ts->_data);
}void Start()
{int n = pthread_create(&_tid, nullptr, ThreadRoution, this);if(n == 0){_isrunning = true;}
}

 为什么我们需要这样设计,在创建时将执行函数设计为静态的,并将this指针作为参数传递给线程执行函数?

原因在于编译器会自动传递一个this指针给类成员函数,并且是第一个参数!所以我们将线程执行函数设计为静态的,静态函数会存在this指针。但我们需要在该函数中调用线程对象的执行方法,所以我们将类对象(this)作为参数传给静态函数!之后在对参数强转即可获得线程对象,然后既可以调用相关执行方法了!

3)线程等待

 只有对启动的线程进行等待才是有意义的!这也是我们为啥在类中添加表示线程状态的成员变量的原因之一!

void Join()
{if(_isrunning){int n = pthread_join(_tid, nullptr);//等待线程,等待成功返回0if(n == 0)_isrunning = false;}
}

4)获取线程名

const std::string& GetThreadName()
{return _threadname;
}

1.3 总体代码

template <class T>
using func_t = std::function<void(T)>;template <class T>
class Thread
{
public:Thread(const std::string& threadname, func_t<T> func, T data):_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data){}static void *ThreadRoution(void *args){Thread *ts = static_cast<Thread *>(args);ts->_func(ts->_data);}void Start(){int n = pthread_create(&_tid, nullptr, ThreadRoution, this);if(n == 0){_isrunning = true;}}void Join(){if(_isrunning){int n = pthread_join(_tid, nullptr);if(n == 0)_isrunning = false;}}const std::string& GetThreadName(){return _threadname;}~Thread(){}
private:pthread_t _tid;std::string _threadname;bool _isrunning;func_t<T> _func;T _data;
};

二、RAII封装锁

 在实际项目中,一般不建议用户自己持有锁,释放锁。这样做的后果就是:一旦加锁和解锁之间的代码抛异常,将会导致死锁,进而导致内存泄漏!

 所以我们一般采用RAII思想。对于临界区加锁时,我们将锁交给对象(LockGuard)来管理。其中LockGuard只存在一个锁成员函数。用户将锁传递给LockGuard对象后,构造函数初始化列表时将锁的对象创建出来,然后在函数体中进行加锁,在析构函数中封装解锁方法!

class Mutex
{
public:Mutex(pthread_mutex_t *lock):_lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);}~Mutex(){}private:pthread_mutex_t *_lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t *lock): _mutex(lock){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}
private:Mutex _mutex;
};

三、随机数运算任务疯转

 下面我们疯转一个类:我们传入两个值和运算符后对数据进行处理。但运算过程中,用户传入运算符可能未知,可能出现除0,模0错误…所以除了运算结果外,我们添加一个_code的成员变量,用于判断结构是否可信!

 然后我们用一个枚举集合表示结果集合(0表示结果可信,非0对应相应错误)。然后我们不仅可以通过_code成员变量判断结果是否可行,一旦结果不可信还可以获取到出错原因。

 这里我们提供了打印任务和打印运算结果接口、仿函数等接口。比较简单,就不多说了!

const int defaultResult = 0;
enum
{ok = 0,div_zero,mod_zero,unknow
};class Task
{
public:Task(){}Task(int data_x, int data_y, char op): _data_x(data_x), _data_y(data_y), _op(op), _result(ok), _code(defaultResult){}void Run(){switch (_op){case '+':_result = _data_x + _data_y;break;case '-':_result = _data_x - _data_y;break;case '*':_result = _data_x * _data_y;break;case '/':{if (_data_y == 0)_code = div_zero;else_result = _data_x / _data_y;break;}case '%':{if (_data_y == 0)_code = mod_zero;else_result = _data_x % _data_y;break;}default:_code = unknow;}}void operator()(){Run();}std::string PrintTask(){std::string s;s += std::to_string(_data_x);s += _op;s += std::to_string(_data_y);s += "=?";return s;}std::string PrintResult(){std::string s;s += std::to_string(_data_x);s += _op;s += std::to_string(_data_y);s += "=";s += std::to_string(_result);s += ", [";s += std::to_string(_code);s += "]";return s;}~Task(){}private:int _data_x;int _data_y;char _op;int _result;int _code; // 错误码为0,结果可行,否则不可信
};

相关文章:

  • 华为校招机试 - 电影知识图谱和查询系统(20240605)
  • @Value获取值和@ConfigurationProperties获取值用法及比较(springboot)
  • 开发框架DevExpress XAF v24.2产品路线图预览——增强跨平台性
  • 医院不良事件监测预警上报系统,PHP不良事件管理系统源码
  • 认识MySQL
  • C++的GUI(图形用户界面)设计工具
  • LeetCode Hot100 二叉搜索树中第K小的元素
  • 探秘企业孵化基地,聚焦国际数字影像产业园
  • Spring有5种自动装配方式,其中autodetect默认使用?
  • 考研数学|《660》《880》怎么搭配使用
  • 【计算机网络】什么是socket编程?以及相关接口详解
  • Java线程池的这几个大坑,你踩过几个?
  • UE5 右键菜单缺少Generate Visual Studio project files
  • serial靶机教程
  • python-报数(赛氪OJ)
  • 【Leetcode】104. 二叉树的最大深度
  • 【个人向】《HTTP图解》阅后小结
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • Flannel解读
  • Java基本数据类型之Number
  • jdbc就是这么简单
  • js正则,这点儿就够用了
  • maven工程打包jar以及java jar命令的classpath使用
  • maya建模与骨骼动画快速实现人工鱼
  • Mithril.js 入门介绍
  • mysql_config not found
  • PAT A1120
  • session共享问题解决方案
  • Vue UI框架库开发介绍
  • 工作中总结前端开发流程--vue项目
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 扑朔迷离的属性和特性【彻底弄清】
  • 前端面试题总结
  • 前端面试之闭包
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • HanLP分词命名实体提取详解
  • ​马来语翻译中文去哪比较好?
  • ​批处理文件中的errorlevel用法
  • "无招胜有招"nbsp;史上最全的互…
  • $L^p$ 调和函数恒为零
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (39)STM32——FLASH闪存
  • (delphi11最新学习资料) Object Pascal 学习笔记---第2章第五节(日期和时间)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (poj1.3.2)1791(构造法模拟)
  • (九)c52学习之旅-定时器
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (四)linux文件内容查看
  • (五)c52学习之旅-静态数码管
  • .helper勒索病毒的最新威胁:如何恢复您的数据?
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .pyc文件是什么?