用pthread_create接管静态成员方法__part1

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() 做的事:

  1. 初始化 pthread_attr_t
  2. 设置线程为 detached:PTHREAD_CREATE_DETACHED
  3. 调用 pthread_create
  4. 等待子线程初始化完成
  5. 确认状态变成 THD_RUNNING_STATE
  6. 返回 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

所以新线程启动后,拿到的 ptlogt() 指向的是同一个 log类的 对象,可以直接调用它的方法(读队列、写日志、更新状态等)。

整体关系图

复制代码
主线程
  logt() ──────────────────────────┐
      ↓                                          │ 同一个对象
  *logt() 传入 attachthread()               │
      ↓                                                  │
  pthread_create(pt) ─────→ 日志线程
                                                │
                                        start_routine(pt)
                                                ↓
                                      pt->attachthread()  // log类 的业务循环
                                     从队列取日志,写文件...

简单说:*logt() 传进去,就是告诉 Manage------"帮我把这个日志对象跑在一个新线程里"