muduo源码分析:Thread类

代码文件目录为:muduo/base

ThreadNameInitializer

ThreadNameInitializer进行主线程初始化操作(利用全局变量):包括设置默认的线程name、缓存线程id。如果进行了fork,那么在子进程中运行afterFork函数进行同样的初始化工作。

void afterFork()
{
  muduo::CurrentThread::t_cachedTid = 0;
  muduo::CurrentThread::t_threadName = "main";
  CurrentThread::tid();
  // no need to call pthread_atfork(NULL, NULL, &afterFork);
}

class ThreadNameInitializer
{
 public:
  ThreadNameInitializer()
  {
    muduo::CurrentThread::t_threadName = "main";
    CurrentThread::tid();
    pthread_atfork(NULL, NULL, &afterFork);
  }
};

ThreadNameInitializer init;

Thread

Thread.h

class Thread : boost::noncopyable        //不允许复制
{
 public:
  typedef boost::function<void ()> ThreadFunc;

  explicit Thread(const ThreadFunc&, const string& name = string());
#ifdef __GXX_EXPERIMENTAL_CXX0X__
  explicit Thread(ThreadFunc&&, const string& name = string());
#endif
  ~Thread();

  void start();		// 启动线程
  int join();		// return pthread_join()

  bool started() const { return started_; }
  // pthread_t pthreadId() const { return pthreadId_; }
  pid_t tid() const { return tid_; }            //返回线程id
  const string& name() const { return name_; }    //返回线程名

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

 private:
  void setDefaultName();

  bool       started_;                   //启动标识
  bool       joined_;
  pthread_t  pthreadId_;		// pthread_t给pthraed_xxx函数使用
  pid_t      tid_;			// pid_t作为线程标识
  ThreadFunc func_;
  string     name_;
  CountDownLatch latch_;

  static AtomicInt32 numCreated_;	//记录创建了多少线程
};

Thread既有pthread_t也有pid_t,它们各有用处,pthread_t给pthread_XXX函数使用,而pid_t作为线程标识。


为什么使用pid_t而不使用pthread_t来标识线程id呢?

pthread_t的值很大,无法作为一些容器的key值。 glibc的Pthreads实现实际上把pthread_t作为一个结构体指针,指向一块动态分配的内存,但是这块内存是可以反复使用的,也就是说很容易造成pthread_t的重复。也就是说pthreads只能保证同一进程内,同一时刻的各个线程不同;不能保证同一个进程全程时段每个线程具有不同的id,不能保证线程id的唯一性。

在LINUX系统中,建议使用gettid()系统调用的返回值作为线程id,这么做的原因: 
返回值是一个pid_t,其值是一个很小的整数,方便输出。 
在linux系统中,它直接标识内核任务调度id,可通过/proc文件系统中找到对应项:/proc/tid 或者 /proc/pid/task/tid,方便定位到具体线程。任何时刻都是唯一的,并且由于linux分配新的pid采用递增轮回办法,短时间内启动多个线程也会具有不同的id。


构造函数Thread::Thread()

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

线程池中调用Thread构造函数如下:

//将this分配给runInThread,相当于构造Thread(this.runInThread,name+id)并加入线程数组。线程函数是runInThread 
threads_.push_back(new muduo::Thread(
          boost::bind(&ThreadPool::runInThread, this), name_+id));	

    threads_[i].start();	//启动每个线程,但是由于线程运行的函数是runInThread,所以会阻塞

线程函数即为:this.runInThread,线程名为name+id。  (注意此处的runInThread不是Thread类中的,而是ThreadPool中的)

线程的默认name与它是第几个线程相关,std::string 没有format,那么格式化可以使用snprintf,或者使用ostringstream,或者boost::format也是可以的。

void Thread::setDefaultName()
{
  int num = numCreated_.incrementAndGet();
  if (name_.empty())
  {
    char buf[32];
    snprintf(buf, sizeof buf, "Thread%d", num);
    name_ = buf;
    //name_ = str(boost::format("Thread%1%") % num); // 使用boost::format
  }
}

Thread::start()

线程启动函数,调用pthread_create创建线程,线程函数为detail::startThread,传递给线程函数的参数data是在heap上分配的,data存放了线程真正要执行的函数记为func、线程id、线程name等信息。detail::startThread会调用func启动线程,所以detail::startThread可以看成是一个跳板或中介。

void Thread::start()			//线程启动函数,调用pthread_create创建线程
{
  assert(!started_);			//确保线程没有启动
  started_ = true;		        //设置标记,线程已经启动 
  // FIXME: move(func_)
  detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_); //data存放了线程真正要执行的函数,记为func,线程id,线程name等信息 

  //创建线程:线程函数为detail::startThread
  if (pthread_create(&pthreadId_, NULL, &detail::startThread, data))
  {
    started_ = false;	//创建线程失败,设置标记线程未启动		
    delete data;		// or no delete?
    LOG_SYSFATAL << "Failed in pthread_create";
  }
  else
  {
    latch_.wait();
    assert(tid_ > 0);
  }
}

detail::startThread首先将参数转型为ThreadData*,然后调用data->runInThread()

void* startThread(void* obj)
{
  ThreadData* data = static_cast<ThreadData*>(obj);
  data->runInThread();
  delete data;
  return NULL;
}

runInThread()最终会调用func()。 (真正的线程函数)

  void runInThread()
  {
    *tid_ = muduo::CurrentThread::tid();
    tid_ = NULL;
    latch_->countDown();
    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
    }
  }

ThreadData

struct ThreadData
{
  typedef muduo::Thread::ThreadFunc ThreadFunc;
  ThreadFunc func_;
  string name_;
  pid_t* tid_;
  CountDownLatch* latch_;

  ThreadData(const ThreadFunc& func,
             const string& name,
             pid_t* tid,
             CountDownLatch* latch)
    : func_(func),
      name_(name),
      tid_(tid),
      latch_(latch)
  { }
  //....

注意:

为什么不能直接在创建线程的时候执行某个类的成员函数?

因为pthread_create的线程函数定义为void *func(void*),无法将non-staic成员函数传递给pthread_create

试想,如果pthread_create的线程函数参数定义为boost::function<void*(void*)>,那么结合boost::bind,就可以将一个成员函数作为参数了,像这样:

pthread_create(&tid, NULL, boost::bind(&Class::func, &obj, _1), arg);

所以boost::function和boost::bind还是挺强大的。在C++11中已经成为标准纳入到std中了。

猜你喜欢

转载自blog.csdn.net/amoscykl/article/details/83623480