muduo源码分析之Thread线程

相关文件

muduo/base/Thread.h//线程类
muduo/base/Thread.cc

muduo/base/currentThread.h //线程真实pid,即tid
muduo/base/cuurentThread.cc

作用

开启一个线程,执行用户函数。

使用

#include <muduo/base/Thread.h>
#include <boost/bind.hpp>
#include <unistd.h>
#include <iostream>
using namespace std;

using namespace muduo;

class Foo
{
public:
  Foo(int count) : count_(count)
  {
  }

  void MemberFun()
  {
    while (count_--)
    {
      cout<<"this is a test ..."<<endl;
      sleep(1);
    }
  }

  void MemberFun2(int x)
  {
    while (count_--)
    {
      cout<<"x="<<x<<" this is a test2 ..."<<endl;
      sleep(1);
    }
  }

  int count_;
};

void ThreadFunc()
{
  cout<<"ThreadFunc ..."<<endl;
}

void ThreadFunc2(int count)
{
  while (count--)
  {
    cout<<"ThreadFunc2 ..."<<endl;
    sleep(1);
  }
}


int main(void)
{
  Thread t1(ThreadFunc); //无参函数
  Thread t2(boost::bind(ThreadFunc2, 3));//有参函数
  Foo foo(3);
  Thread t3(boost::bind(&Foo::MemberFun, &foo));//无参类方法
  Foo foo2(3);
  Thread t4(boost::bind(&Foo::MemberFun2, &foo2, 1000));//有参类方法

  t1.start(); //开始执行
  t2.start();
  t3.start();
  t4.start();

  t1.join(); //阻塞等待线程结束
  t2.join();
  t3.join();
  t4.join();

  return 0;
}

Thread源码分析

Thread类

类图
image

这里涉及到一个线程标识符tid
Linux中,每个进程有一个pid,类型pid_t,由getpid()取得。Linux下的POSIX线程也有一个id,类型 pthread_t,由pthread_self()取得,该id由线程库维护,其id空间是各个进程独立的(即不同进程中的线程可能有相同的id)。Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),只是该进程与主进程(启动线程的进程)共享一些资源而已,比如代码段,数据段等。
有时候我们可能需要知道线程的真实pid。比如进程P1要向另外一个进程P2中的某个线程发送信号时,既不能使用P2的pid,更不能使用线程的pthread id,而只能使用该线程的真实pid,称为tid。
有一个函数gettid()可以得到tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall来获取。
return syscall(SYS_gettid)

class Thread : noncopyable
{
 public:
  typedef std::function<void ()> ThreadFunc;  //线程函数

  explicit Thread(ThreadFunc, const string& name = string()); //构造
  ~Thread(); //析构

  void start();
  int join(); // return pthread_join()

  bool started() const { return started_; }
  pid_t tid() const { return tid_; }   //线程真实pid
  const string& name() const { return name_; } //线程名字

  static int numCreated() { return numCreated_.get(); } //已经创建的线程数

 private:
  void setDefaultName();  //设置默认name_

  bool       started_;  
  bool       joined_;
  pthread_t  pthreadId_;
  pid_t      tid_;     //线程标识符
  ThreadFunc func_;
  string     name_;     
  CountDownLatch latch_; //计数+条件变量,计数等于0时条件变量做通知

  static AtomicInt32 numCreated_; //创建线程数,原子性操作
};

构造函数

主要对数据成员进行初始化,还没创建线程执行入口函数。

Thread::Thread(ThreadFunc func, const string& n)
  : started_(false),
    joined_(false),
    pthreadId_(0),
    tid_(0),
    func_(std::move(func)),
    name_(n),
    latch_(1)
{
  setDefaultName();
}

void Thread::setDefaultName()
{
  int num = numCreated_.incrementAndGet();
  if (name_.empty()) //没有设置名字,使用默认的name
  {
    char buf[32];
    snprintf(buf, sizeof buf, "Thread%d", num);
    name_ = buf;
  }
}

start()方法

将数据封装在ThreadData类,执行pthread_create创建线程。

void Thread::start()
{
  assert(!started_);
  started_ = true;
  // FIXME: move(func_)
  detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_);
  if (pthread_create(&pthreadId_, NULL, &detail::startThread, data))
  {
    started_ = false;
    delete data; // or no delete?
    LOG_SYSFATAL << "Failed in pthread_create";
  }
  else
  {
    latch_.wait(); //主线程等待子线程初始化完毕才开始工作,在runInThread()中
    assert(tid_ > 0);
  }
}

线程执行函数
使用了CountDownLatch类,这个类主要成员是计数值和条件变量,当计数值为0,条件变量发出通知。
既可以用于所有子线程等待主线程发起 “起跑” ;
也可以用于主线程等待子线程初始化完毕才开始工作。

//线程入口函数
//
void* startThread(void* obj)
{
  ThreadData* data = static_cast<ThreadData*>(obj);
  data->runInThread();
  delete data;
  return NULL;
}

 void runInThread()
 {
    *tid_ = muduo::CurrentThread::tid();//Thread类获取tid
    tid_ = NULL;
    latch_->countDown();//计数减一,因为初始化为1,为零时条件变量通知主线程初始化完成
    latch_ = NULL;

    muduo::CurrentThread::t_threadName = name_.empty() ? "muduoThread" : name_.c_str();
    ::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName);
    try
    {
      func_();//执行用户函数
      muduo::CurrentThread::t_threadName = "finished";
    }
    catch (const Exception& ex) //捕捉异常
    {
      muduo::CurrentThread::t_threadName = "crashed";
      fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
      fprintf(stderr, "reason: %s\n", ex.what());
      fprintf(stderr, "stack trace: %s\n", ex.stackTrace());
      abort();
    }
    catch (const std::exception& ex)
    {
      muduo::CurrentThread::t_threadName = "crashed";
      fprintf(stderr, "exception caught in Thread %s\n", name_.c_str());
      fprintf(stderr, "reason: %s\n", ex.what());
      abort();
    }
    catch (...)
    {
      muduo::CurrentThread::t_threadName = "crashed";
      fprintf(stderr, "unknown exception caught in Thread %s\n", name_.c_str());
      throw; // rethrow
    }
  }
};

tid的获取

在ThreadData::runInThread()中调用了CurrentThread::tid()给Thread获取线程tid。
前面说到,有一个函数gettid()可以得到tid,但glibc并没有实现该函数,只能通过Linux的系统调用syscall来获取。
muduo中使用系统调用获取tid后保存在__thread变量中,减少多次系统调用。
CurrentThread并是不一个类,该命名空间中的是线程自己保留的变量,用__thread关键字修饰。
__thread变量是每个线程都有一份独立实体,各个线程的变量值互不干扰。

  extern __thread int t_cachedTid;
  extern __thread char t_tidString[32];
  extern __thread int t_tidStringLength;
  extern __thread const char* t_threadName;

CurrentThread::tid()

//CurrentThread.h
inline int tid()
 {
    if (__builtin_expect(t_cachedTid == 0, 0))
    {
      cacheTid();
    }
    return t_cachedTid;
 }

//Thread.h
void CurrentThread::cacheTid()
{
  if (t_cachedTid == 0)
  {
    t_cachedTid = detail::gettid();
    t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid);
  }
}
//Thread.h
//namespace detail

pid_t gettid()
{ 
  //线程的真实tid
  return static_cast<pid_t>(::syscall(SYS_gettid));
}
posted @ 2021-06-09 00:07  零十  阅读(228)  评论(0编辑  收藏  举报