muduo网络库剖析——线程Thread类
- 前情
- 从muduo到my_muduo
- 概要
- 框架与细节
- 成员
- 函数
- 使用方法
- 源码
- 结尾
前情
从muduo到my_muduo
作为一个宏大的、功能健全的muduo库,考虑的肯定是众多情况是否可以高效满足;而作为学习者,我们需要抽取其中的精华进行简要实现,这要求我们足够了解muduo库。
做项目 = 模仿 + 修改,不要担心自己学了也不会写怎么办,重要的是积累,学到了这些方法,如果下次在遇到通用需求的时候你能够回想起之前的解决方法就够了。送上一段话!
概要
Thread类实际上是对应的one loop one thread中的thread,那么相应的thread也有自己的一些成员与函数。
框架与细节
成员
首先thread也有判断是否已经启动的状态位started,以及是否和父进程分离开来的状态位。以及用RAII机制来给thread进行资源管理。因为允许thread被多个对象指,所以对应的是共享指针而不是独享指针。接着是thread的线程号,这个是用来判断one loop one thread的。还有线程执行函数func_,线程的名字,有多少个线程被创建了。这个值不是单个线程独享的,所以定义为static静态变量,为了确保线程安全性,使用原子数据类型atomic_int。
对于atomic_int的初始化,首先肯定是在类外进行初始化的。但是这里因为atomic_int的拷贝构造函数默认是delete的,所以我们只能用构造函数的方式去初始化而不能用拷贝构造函数的方式去初始化。因此是numCreated_(0),而不是numCreated_ = 0.
函数
接下来是,Thread的构造函数实现。传参肯定需要线程执行函数回调,以及线程名字。并且调用设置默认名字函数。func_使用右值赋值。
在设置默认名字函数内,我们同时进行对线程创建数目的增加统计,如果传入的线程名字为空,就给默认的线程名字,否则就是传入的线程名。
接下来是线程类的析构函数,首先得先确认状态,如果是开始态,并且线程没有被分离,则线程需要分离,就调用thread自带的分离函数进行分离。
首先将thread启动状态位置成true。既然要启动,肯定需要让一个新线程启动起来。对与指向thread的共享智能指针,给它赋值的方式是new一个堆上的空间。因此使用shared_ptr< thread >(new thread());来创建新线程,并将指针赋给thread_。对于thread,他的创建方式这里采用c++11中的匿名函数。引用捕获变量,在匿名函数体内,我们需要记录此时的线程号。这里的好处类的作用域能够进入到新线程里,所以tid_不需要加类域。可以学习一下这种写法。除此以外,我们还要在线程中执行线程的执行回调函数。另外一个重点是,为了在start函数结束之后,tid已经是确定值,我们使用一个线程同步的方式来保证指令执行的顺序。所以我们使用了一个sem信号量。信号量的初始值是0,这样必须要等到信号量post之后才能wait到信号量。对于sem_init,第二个参数是询问是否需要用到sem给到的默认初始值,选择false就需要初始化第三个参数。
线程的分离也需要单独创建函数,其中将线程的状态位置1,并调用thread自带的join分离函数即可。
另外,定义一个CurrentThread的命名空间,其中实现了查询现在的线程的id是多少,封装在里面的是一个系统调用,大家可以学习一下。
使用方法
源码
//Thread.h
#pragma once
#include <sys/syscall.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
namespace CurrentThread {
int t_cachedTid = 0;
inline int tid() {
if (t_cachedTid == 0) {
t_cachedTid = static_cast<pid_t>(::syscall(SYS_gettid));
}
return t_cachedTid;
}
};
class Thread : noncopyable
{
public:
using ThreadFunc = std::function<void()>;
explicit Thread(ThreadFunc, const std::string &name = std::string());
~Thread();
void start();
void join();
bool started() const { return started_; }
pid_t tid() const { return tid_; }
const std::string& name() const { return name_; }
static int numCreated() { return numCreated_; }
private:
void setDefaultName();
bool started_;
bool joined_;
std::shared_ptr<std::thread> thread_;
pid_t tid_;
ThreadFunc func_;
std::string name_;
static std::atomic_int numCreated_;
};
//Thread.cc
#include <semaphore.h>
#include "Thread.h"
std::atomic_int Thread::numCreated_(0);
Thread::Thread(ThreadFunc func, const std::string &name)
: started_(false)
, joined_(false)
, tid_(0)
, func_(std::move(func))
, name_(name) {
setDefaultName();
}
Thread::~Thread() {
if (started_ && !joined_)
{
thread_->detach(); // thread类提供的设置分离线程的方法
}
}
// 一个Thread对象,记录的就是一个新线程的详细信息
void Thread::start() {
started_ = true;
sem_t sem;
sem_init(&sem, false, 0);
// 开启线程
thread_ = std::shared_ptr<std::thread>(new std::thread([&](){
// 获取线程的tid值
tid_ = CurrentThread::tid();
sem_post(&sem);
// 开启一个新线程,专门执行该线程函数
func_();
}));
// 这里必须等待获取上面新创建的线程的tid值
sem_wait(&sem);
}
void Thread::join() {
joined_ = true;
thread_->join();
}
void Thread::setDefaultName() {
int num = ++numCreated_;
if (name_.empty())
{
char buf[32] = {0};
snprintf(buf, sizeof buf, "Thread%d", num);
name_ = buf;
}
}
结尾
以上就是线程Thread类的相关介绍,以及我在进行项目重写的时候遇到的一些问题,和我自己的一些心得体会。发现写博客真的会记录好多你的成长,而且对于一个好的项目,写博客也是证明你确实有过深度思考,并且在之后面试或者工作时遇到同样的问题能够进行复盘的一种有效的手段。所以,希望uu们也可以像我一样,养成写博客的习惯,逐渐脱离菜鸡队列,向大佬前进!!!加油!!!
也希望我能够完成muduo网络库项目的深度学习与重写,并在功能上能够拓展。也希望在完成这个博客系列之后,能够引导想要学习muduo网络库源码的人,更好地探索这篇美丽繁华的土壤。致敬chenshuo大神!!!
鉴于博主只是一名平平无奇的大三学生,没什么项目经验,所以可能很多东西有所疏漏,如果有大神发现了,还劳烦您在评论区留言,我会努力尝试解决问题!