preface
c
#ifndef __MANAGE_HPP__
#define __MANAGE_HPP__
#include "loglib.hpp"
#include "commonlib.hpp"
class Empty {
};
template<class thd, class DATA = Empty>
class Manage {
public:
//virtual int createthd(thd *&t, DATA *para = NULL, string str = "");
//attach and run thread
static int attachthread(thd &t);
//quit thread
static int quit(thd &t);
//del thread object
static int del(thd *t);
};
#include "manage.inl"
#endif
c
//log类静态成员对象完成日志级别、文件等设置,
//log是类名, logt指向单例log类静态成员,代表一个指针
if(0 != Manage<log>::attachthread(*logt()))
{
//cout 运行日志thread失败
return -1;
}
//现在可以使用log2file把日志写入日志文件了
// 调用log2file(),写入日志文件
一、
Manage 本身没有创建对象,只负责把已经存在的线程对象交给 pthread_create 启动、设置退出标志、以及删除对象。关键点在于 thd 类型必须提供互斥
锁、条件变量、状态和退出标志这些方法。
Manage是一个模板线程启动/停止工具,
thd 基本上必须是 有这些接口,因为 attachthread() 会调用这些接口:
mutex_acquire_lock(), wait_Condition_Lock(), thdStatus(), signal_Condition_Lock(), mutex_free_lock(), quit_标志位(), thdid()
attachthread并且最终把线程入口交给 start_routine,也就是 源代码 里的全局线程函数。线程真正循环执行的是派生类重写的 运行回调函数(),退出前执行 停止回调函数()。
attachthread() 做的事:
- 初始化 pthread_attr_t
- 设置线程为 detached:PTHREAD_CREATE_DETACHED
- 调用 pthread_create
- 等待子线程初始化完成
- 确认状态变成 THD_RUNNING_STATE
- 返回 0
二、
三、
从两个角度来表述
1、为什么要创建新线程?
日志系统需要单独一个线程,核心原因是异步写日志。
如果没有独立线程(同步写日志)
业务线程
↓
调用 log2file()
↓
直接写磁盘 ← 磁盘 I/O 很慢,可能几毫秒到几十毫秒
↓
写完才能继续业务逻辑 ← 业务被阻塞了!
有了独立日志线程(异步写日志)
业务线程 日志线程(独立运行)
↓ ↓
调用 log2file() 不断从队列取日志
↓ ↓
把日志消息塞进队列 写入磁盘文件
↓(立刻返回,不等待)
继续执行业务逻辑
好处很明显:
- 业务线程不被 I/O 阻塞,性能更好
- 日志线程专职写盘,有序、不竞争
- 多个业务线程都可以同时往队列里丢日志,日志线程统一消费
2、*logt() 传进 attachthread() 有什么用?
先看调用:
cpp
Manage<log>::attachthread(*logt());
// ↑
// 解引用,得到 log类的 对象的引用
attachthread() 的签名是:
cpp
static int attachthread(thd &t); // 接收的是引用
所以 *logt() 传进去之后,在 attachthread() 内部就是 t,它的作用贯穿整个函数:
cpp
// 1. 用它来同步(父子线程握手)
t.lock();
t.wait_cond();
t.sig_cond();
t.unlock();
// 2. 把它的指针传给新线程作为参数
thd *pt = dynamic_cast<thd*>(&t);
pthread_create(&tid, &a, start_routine, (void *)pt);
// ↑
// 新线程拿到这个指针后
// 就能操作 log类的 对象本身
关键点:新线程拿到的是同一个对象
logt() 返回 log类 的单例指针
*logt() 解引用为对象引用,传入 attachthread()
&t → pt 在 attachthread() 内部取回指针
pthread_create 把 pt 传给新线程的入口函数 newthreadname
所以新线程启动后,拿到的 pt 和 logt() 指向的是同一个 log类的 对象,可以直接调用它的方法(读队列、写日志、更新状态等)。
整体关系图
主线程
logt() ──────────────────────────┐
↓ │ 同一个对象
*logt() 传入 attachthread() │
↓ │
pthread_create(pt) ─────→ 日志线程
│
start_routine(pt)
↓
pt->attachthread() // log类 的业务循环
从队列取日志,写文件...
简单说:*logt() 传进去,就是告诉 Manage------"帮我把这个日志对象跑在一个新线程里"。