sylar作者在本站的地址为 这里,也可以查看 作者主页,也有视频教程可以 点击这里。
一、 进程与线程
- 进程: 在操作系统中, 每个运行的应用程序就是一个进程。每个进程都有自己的内存空间,不同进程间的内存空间是相互隔离的,每个进程信息都保存在操作系统的内核空间中。
- 线程:就是进程中的执行体,他需要指定的执行入口,通常为某个函数的指令入口。一个进程至少有一个线程,他要从这个线程开始执行,一般被称为进程的主线程,可以认为主线程是进程的第一个线程,一般是由父进程或者操作系统直接创建的。一个进程也可以有多个线程,而这些额外的线程都是由主线程创建的,这些线程可以共享进程的地址空间和句柄表等资源。既然可以共享进程中的资源,就免不了线程安全方面的的设计。
二、Sylar源码分析
namespace sylar {
//线程类
class Thread : Noncopyable {
public:
// 线程智能指针类型
typedef std::shared_ptr<Thread> ptr;
// 线程构造函数
// cb 线程执行函数
// name 线程名称
Thread(std::function<void()> cb, const std::string& name);
~Thread();
// 获取线程Id
pid_t getId() const { return m_id;}
// 获取线程姓名
const std::string& getName() const { return m_name;}
// 等待线程执行完成
void join();
// 得到当前正在运行的线程指针
static Thread* GetThis();
// 得到当前线程名称
static const std::string& GetName();
// 设置当前线程名称
static void SetName(const std::string& name);
private:
static void* run(void* arg);
private:
// 线程Id
pid_t m_id = -1;
// 线程结构句柄
pthread_t m_thread = 0;
// 线程执行函数体
std::function<void()> m_cb;
// 线程名称
std::string m_name;
// 信号量
Semaphore m_semaphore;
};
}
以下一小段是sylar作者原话
线程模块,封装了pthread里面的一些常用功能。 Thread,Semaphore,Mutex,RWMutex,Spinlock等对象,可以方便开发中对线程日常使用
为什么不适用c++11里面的thread
本框架是使用C++11开发,不使用thread,是因为thread其实也是基于pthread实现的。并且C++11里面没有提供读写互斥量,RWMutex,Spinlock等,在高并发场景,这些对象是经常需要用到的。所以选择了自己 封装pthread
接下来介绍一下线程模块使用的信号量的封装
class Semaphore : Noncopyable {
public:
/**
* @brief 构造函数
* @param[in] count 信号量值的大小
*/
Semaphore(uint32_t count = 0);
/**
* @brief 析构函数
*/
~Semaphore();
/**
* @brief 获取信号量
*/
void wait();
/**
* @brief 释放信号量
*/
void notify();
private:
sem_t m_semaphore;
};
// 以下是函数实现
Semaphore::Semaphore(uint32_t count) {
if(sem_init(&m_semaphore, 0, count)) {
throw std::logic_error("sem_init error");
}
}
/* throw用法简单介绍
C++通过 “throw” 关键字 抛出一条表达式来触发一个异常
它通常作为 条件语句的一部分 或者 作为某个函数的最后一条语句,
当throw执行时,跟在throw后面的语句将不再被执行,
程序的控制权从throw转移到与之匹配的catch
(catch可能是同一个函数中的局部catch,也可能位于调用链上的其他函数)。
try {
throw expression;
}
catch(case 1) {
}
catch(case 2) {
}
catch(case 3) {
}
所谓 “try”,就是 “尝试着执行一下”,如果有异常,
则通过throw向外抛出,随后在外部通过catch捕获并处理异常。
*/
// 简单说了一下抛出异常机制,感兴趣的小伙伴可以去详细的查一下相关资料
Semaphore::~Semaphore() {
sem_destroy(&m_semaphore);
}
void Semaphore::wait() {
if(sem_wait(&m_semaphore)) {
throw std::logic_error("sem_wait error");
}
}
void Semaphore::notify() {
if(sem_post(&m_semaphore)) {
throw std::logic_error("sem_post error");
}
}
下面是线程类的函数实现
namespace sylar {
// 当前线程的指针
static thread_local Thread* t_thread = nullptr;
static thread_local std::string t_thread_name = "UNKNOW";
// thread_local变量,就是线程局部变量,意味着这个变量是线程独有的,
// 是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。
// static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
// 源码这里是使用作者前面实现的log类来打印日志调试的,
// 我在调试时没有使用作者的这个log类,就只使用了std::cout来打印调试信息。
// 获取当前线程指针
Thread* Thread::GetThis() {
return t_thread;
}
// 获取当前线程名称
const std::string& Thread::GetName() {
return t_thread_name;
}
// 设置当前线程姓名
void Thread::SetName(const std::string& name) {
if(name.empty()) {
return;
}
if(t_thread) {
t_thread->m_name = name;
}
t_thread_name = name;
}
// 构造函数
Thread::Thread(std::function<void()> cb, const std::string& name)
:m_cb(cb)
,m_name(name) {
if(name.empty()) {
m_name = "UNKNOW";
}
int rt = pthread_create(&m_thread, nullptr, &Thread::run, this);
/* pthread_create的用法
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
第一个参数用于存放指向pthread_t类型的指针(指向该线程tid号的地址)
第二个参数表示了线程的属性,一般以NULL表示默认属性
第三个参数是一个函数指针,就是线程执行的函数。这个函数返回值为 void*,形参为 void*
第四个参数则表示为向线程处理函数传入的参数,若不传入,可用 NULL 填充
这里是将类的this指针传递进去
*/
if(rt) {
SYLAR_LOG_ERROR(g_logger) << "pthread_create thread fail, rt=" << rt
<< " name=" << name;
throw std::logic_error("pthread_create error");
}
m_semaphore.wait();
// 消耗一个信号量,需要配合释放信号量一同使用,否则其他进程将会阻塞等待信号量
}
Thread::~Thread() {
if(m_thread) {
pthread_detach(m_thread);
//线程分离状态:指定该状态,线程主动与主控线程断开关系。
//线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
}
}
void Thread::join() {
if(m_thread) {
int rt = pthread_join(m_thread, nullptr);
/* 当A线程调用线程B并 pthread_join() 时,
A线程会处于阻塞状态,直到B线程结束后,A线程才会继续执行下去。
当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,
它的内存空间也会被释放(如果被调用线程是非分离的)。
*/
if(rt) {
SYLAR_LOG_ERROR(g_logger) << "pthread_join thread fail, rt=" << rt
<< " name=" << m_name;
throw std::logic_error("pthread_join error");
}
m_thread = 0;
}
}
// 线程的执行体
void* Thread::run(void* arg) {
// arg为函数体的执行参数
Thread* thread = (Thread*)arg;
t_thread = thread;
t_thread_name = thread->m_name;
//thread->m_id = sylar::GetThreadId();
thread->m_id = syscall(SYS_gettid);
pthread_setname_np(pthread_self(), thread->m_name.substr(0, 15).c_str());
std::function<void()> cb;
cb.swap(thread->m_cb);// 获得执行函数
thread->m_semaphore.notify(); // 释放信号量
cb(); // 在这里才是真正的执行
return 0;
}
}
下面为测试程序
void fun1() {
std::cout << "[name] " << sylar::Thread::GetName()
<< ", [this.name] " << sylar::Thread::GetThis()->GetName()
<< ", [id] " << syscall(SYS_gettid)
<< ", [this.id] " << sylar::Thread::GetThis()->getId()
<< std::endl;
}
int main(int argc, char** argv) {
std::cout << "[thread test begin]" << std::endl;
std::vector<sylar::Thread::ptr> thrs;
for(int i = 0; i < 5; ++i) {
sylar::Thread::ptr thr(new sylar::Thread(&fun1, "name_" + std::to_string(i * 1)));
thrs.push_back(thr);
}
for(size_t i = 0; i < thrs.size(); ++i) {
thrs[i]->join();
}
std::cout << "[thread test end]" << std::endl;
return 0;
}
执行结果为
不加信号量的执行结果为
可以看到,没有按照线程的创建顺序执行。
总结
- 本文是我第一次分享C++学习的一些笔记,内容中可能有错误的地方,还请多包涵
- 后续在空闲时间将把sylar服务器框架给分享完毕
- 目标是使用sylar框架设计一个mmo类型的多人游戏服务器