muduo源码阅读(3): Thread类

变量numCreated_表示创建的线程个数,类型为AtomicInt32,用到了我们上篇所说的原子性操作。
Thread类中还用到了CurrentThread命名空间的变量和函数。

以下要点参考:https://blog.youkuaiyun.com/Leeds1993/article/details/52156322
(1)线程标识符

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来获取。

syscall函数原型为:

int syscall(int number, ...); 

其中number是系统调用号,number后面应顺序接上该系统调用的所有参数。下面是gettid系统调用的调用实例。

#include <unistd.h> 
#include <sys/syscall.h> 
#include <sys/types.h> 
   
#define    __NR_gettid      186 
   
int main(int argc, char *argv[])  
{  
     pid_t tid;  
   
     tid = syscall(__NR_gettid);  
} 

大部分系统调用都包括了一个SYS_符号常量来指定自己到系统调用号的映射,因此上面第10行可重写为:

tid = syscall(SYS_gettid); 

(2)__thread关键字和POD类型
__thread是GCC内置的线程局部存储设施,存取效率可以和全局变量相比。__thread变量在每一个线程有一份独立实体,各个线程的值互不干扰。可以用来修饰那些带有全局性且值可能变,但是又不值得用全局变量保护的变量。用一个例子来理解它的用法。

#include <pthread.h>
#include <iostream>
#include <unistd.h>

using namespace std;

//__thread int var = 5;
int var = 5;

void *worker1(void* arg);
void *worker2(void* arg);

int main()
{
        pthread_t p1, p2;

        pthread_create(&p1, NULL, worker1, NULL);
        pthread_create(&p2, NULL, worker2, NULL);
        pthread_join(p1, NULL);
        pthread_join(p2, NULL);

        return 0;
}

void *worker1(void* arg)
{
        cout << ++var << endl;
}

void *worker2(void* arg)
{
        cout << ++var << endl;
}

/**
 * 使用__thread关键字,输出为: 
 *                         6
 *                         6
 * 
 * 不使用__thread关键字,输出为:
 *                         6   
 *                         7
 * /

注:__thread只能修饰POD类型,不能修饰class类型,因为无法自动调用构造函数和析构函数。

POD类型(plain old data)是指与C兼容的原始数据类型,例如,结构体和整型等C语言中的类型就是 POD 类型,但带有用户定义的构造函数或虚函数的类则不是:

__thread可以用于修饰全局变量、函数内的静态变量,但是不能用于修饰函数的局部变量或者class的普通成员变量。

另外,__thread变量的初始化只能用编译器常量。

__thread string t_obj1(“hello”);    // 错误,不能调用对象的构造函数
__thread string* t_obj2 = new string;   // 错误,初始化必须用编译期常量
__thread string* t_obj3 = NULL; // 正确,但是需要手工初始化并销毁对象

(3)pthread_atfork()函数

#include <pthread.h>
int pthread_atfork(void (*prepare)(void), 
                    void (*parent)(void), 
                    void (*child)(void));

用法:调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent ,子进程会调用child。

(4)多线程与多进程混合(fork())
对于编写多线程程序来说,最好不要再调用fork(),即不要编写多线程多进程程序。因为Linux的fork()只克隆当前线程的thread of control ,不克隆其他线程。fork()之后,除了当前线程之外,其他线程都消失了,也就是说,不能一下子fork()出一个和父进程一样的多线程子进程。

fork()之后子进程中只有一个线程,其他线程都消失了,这就造成一个危险的局面。其他线程可能正好位于临界区之内,持有了某个锁,而它突然死亡,再也没有机会去解锁了。如果子进程试图再对同一个mutex加锁,就会立刻死锁。

// 一个在多线程程序里fork造成死锁的例子
// 一个输出示例:
/*
pid = 19445 Entering main ...
pid = 19445 begin doit ...
pid = 19447 begin doit ...				//只有begin没有end
pid = 19445 end doit ...
pid = 19445 Exiting main ...
父进程在创建了一个线程,并对mutex加锁,
父进程创建一个子进程,在子进程中调用doit,由于子进程会复制父进程的内存,这时候mutex处于锁的状态,进入doit又遇到lock,
所以就一直等待,直到拥有该互斥体的进程释放它,线程的doit执行完成之前会把自己的mutex释放,但这时的mutex和子进程里的mutex已经是两份内存.
所以即使释放了mutex锁也不会对子进程里的mutex造成什么影响
父进程在复制子进程的时候,只会复制当前线程的执行状态,其它线程不会复制。因此子进程会处于死锁的状态。
*/
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* doit(void* arg)
{
	printf("pid = %d begin doit ...\n", static_cast<int>(getpid()));
	pthread_mutex_lock(&mutex);
	struct timespec ts = { 2, 0 };//sleep 2s
	nanosleep(&ts, NULL);
	pthread_mutex_unlock(&mutex);
	printf("pid = %d end doit ...\n", static_cast<int>(getpid()));

	return NULL;
}

int main(void)
{
	printf("pid = %d Entering main ...\n", static_cast<int>(getpid()));
	pthread_t tid;
	pthread_create(&tid, NULL, doit, NULL);
	struct timespec ts = { 1, 0 };//sleep 1s
	nanosleep(&ts, NULL);
	if (fork() == 0)
	{
		doit(NULL);
	}
	pthread_join(tid, NULL);
	printf("pid = %d Exiting main ...\n", static_cast<int>(getpid()));

	return 0;
}

解决方法:

#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* doit(void* arg)
{
	printf("pid = %d begin doit ...\n", static_cast<int>(getpid()));
	pthread_mutex_lock(&mutex);
	struct timespec ts = { 2, 0 };
	nanosleep(&ts, NULL);
	pthread_mutex_unlock(&mutex);
	printf("pid = %d end doit ...\n", static_cast<int>(getpid()));

	return NULL;
}

void prepare(void)
{
	pthread_mutex_unlock(&mutex);
}

void parent(void)
{
	pthread_mutex_lock(&mutex);
}

int main(void)
{
	pthread_atfork(prepare, parent, NULL);				
							//调用fork时,内部创建子进程前在父进程中会调用prepare(unlock),内部创建子进程成功后,父进程
                           //会调用parent(lock),子进程会调用child
	printf("pid = %d Entering main ...\n", static_cast<int>(getpid()));
	pthread_t tid;
	pthread_create(&tid, NULL, doit, NULL);
	struct timespec ts = { 1, 0 };
	nanosleep(&ts, NULL);
	if (fork() == 0)
	{
		doit(NULL);
	}
	pthread_join(tid, NULL);
	printf("pid = %d Exiting main ...\n", static_cast<int>(getpid()));

	return 0;
}


(5)share_ptr和weak_ptr

muduo 库中 Thread 类里要把 tid_ 用一个 shared_ptr 包装起来,boost::shared_ptr<pid_t> tid_; (源自Thread.h中的成员变量)。

结构体ThreadData构造函数中用weak_ptr<pid_t> wkTid_来接受shared_ptr<pid_t> tid_,然后在线程入口函数runInThread中boost::shared_ptr<pid_t> ptid = wkTid_.lock();把weak_ptr转换成shared_ptr。这么做是为什么?

这个tid_是用来存放线程的真实进程Id(共享数据),只能在线程内通过系统调用SYS_gettid获得,tid所属的对象Thread在主线程A中创建,而tid_的需要在新创建的线程B中进行赋值操作,如果tid_使用裸指针的方式传递给线程B,那么线程A中Thread对象析构销毁后,线程B持有的就是一个野指针。muduo库中的使用方式是,在Thread对象中将tid_以shared_ptr包装,线程B使用传递进来的shared_ptr构造一个weak_ptr持有,在需要进行赋值操作时,通过weak_ptr的lock得到一个shared_ptr然后操作,如果此时Thread对象已经销毁,lock得到的是一个空shared_ptr,判断之后不进行操作即可(线程安全问题)

自己的另一篇介绍智能指针博客test3描述这个问题

struct ThreadData                            //线程数据结构体放了线程真正要执行的函数,记为func_,线程id,线程name等信息
{
    typedef muduo::Thread::ThreadFunc ThreadFunc;
    ThreadFunc func_;
    string name_;
    boost::weak_ptr<pid_t> wkTid_;		//weak_ptr接收

    ThreadData(const ThreadFunc& func, 
        const string& name,
        const boost::shared_ptr<pid_t>& tid)
        : func_(func),
        name_(name),
        wkTid_(tid)
    {
    }

    void runInThread()
    {
        pid_t tid = muduo::CurrentThread::tid(); //获取线程id(在CurrentThread.h文件)

        boost::shared_ptr<pid_t> ptid = wkTid_.lock();//if此时Thread对象已经销毁,lock得到的是一个空shared_ptr
        if (ptid)			
        {
            *ptid = tid;//把共享的对象值改为tid
            ptid.reset(); //销毁shared_ptr<pid_t>ptid,其他共享对象引用计数-1
        }

        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";
        }
        ...
        ...
    }
};


代码部分:

Thread.h

// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)

#ifndef MUDUO_BASE_THREAD_H
#define MUDUO_BASE_THREAD_H

#include <muduo/base/Atomic.h>
#include <muduo/base/Types.h>

#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <pthread.h>

namespace muduo
{

    class Thread : boost::noncopyable
    {
    public:
        typedef boost::function<void()> ThreadFunc;
  //boost::function<void ()>相当于定义了一个更加广义的函数指针,一般函数指针需要指定入口参数类型和返回参数类型,但是用boost
  //并且入口参数和返回参数都为空,就适用于所有函数,但前提是需要绑定
        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_; }

        pid_t tid() const { return *tid_; }          //返回线程id
        const string &name() const { return name_; } //返回"Thread%d"

        static int numCreated() { return numCreated_.get(); } //返回创建的线程数(get是AtomicIntegerT类成员函数)

    private:
        void setDefaultName();

        bool started_; //启动标识
        bool joined_;
        pthread_t pthreadId_;
        boost::shared_ptr<pid_t> tid_; //见下面解析
        ThreadFunc func_;
        string name_;                               //  "Thread%d"

        static AtomicInt32 numCreated_; //记录创建了多少线程(每构造一次Thread类,静态变量加1)
    };

} // namespace muduo
#endif
/*
为什么 muduo 库中 Thread 类里要把 tid 用一个 shared_ptr 包装起来?
	这个tid是用来存放线程的真实进程Id,只能在线程内通过系统调用SYS_gettid获得,tid所属的对象Thread在主线程A中创建,
	而tid的需要在新创建的线程B中进行赋值操作,如果tid使用裸指针的方式传递给线程B,那么线程A中Thread对象析构销毁后,
	线程B持有的就是一个野指针。muduo库中的使用方式是,在Thread对象中将tid以shared_ptr包装,线程B使用传递进来的shared_ptr
	构造一个weak_ptr持有,在需要进行赋值操作时,通过weak_ptr的lock得到一个shared_ptr然后操作,如果此时Thread对象已经销毁,
	lock得到的是一个空shared_ptr,判断之后不进行操作即可(线程安全问题)


*/

CurrentThread.h

// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)

#ifndef MUDUO_BASE_CURRENTTHREAD_H
#define MUDUO_BASE_CURRENTTHREAD_H

#include <stdint.h>

namespace muduo
{
    namespace CurrentThread
    {
        // internal
        extern __thread int t_cachedTid;			//缓存的线程号,缓存前是0
        extern __thread char t_tidString[32];		//线程号的字符串形似
        extern __thread const char *t_threadName;   //线程名字
        void cacheTid();

        inline int tid()
        { //__builtin_expect 是 GCC (version >= 2.96)提供给程序员使用的,目的是将“分支转移”的信息提供给编译器,
            //   这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降
            //__builtin_expect((x),1)表示 x 的值为真的可能性更大
            if (__builtin_expect(t_cachedTid == 0, 0)) //大部分情况下t_cachedTid==0为假,即已经缓存过了就不需要缓存了
            {
                cacheTid(); //定义在Thread.cc
            }
            return t_cachedTid;
        }

        inline const char *tidString() // for logging
        {
            return t_tidString;
        }

        inline const char *name()
        {
            return t_threadName;
        }

        bool isMainThread(); //定义在Thread.cc 判断是否主线程

        void sleepUsec(int64_t usec); //定义在Thread.cc
    }                                 // namespace CurrentThread
} // namespace muduo

#endif

上面两个头文件大多都定义在下面
Thread.cc

// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)

#include <muduo/base/Thread.h>
#include <muduo/base/CurrentThread.h>
#include <muduo/base/Exception.h>
#include <muduo/base/Logging.h>

#include <boost/static_assert.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/weak_ptr.hpp>

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <linux/unistd.h>

namespace muduo
{
    namespace CurrentThread
    {
        //以下3个变量来自CTurrenthread.h下CurrentThread命名空间的extern变量 
                                     //__thread修饰的变量是线程局部存储的,只能修饰POD类型(plain old data),与C兼容的原始数据
        __thread int t_cachedTid = 0;  //线程真实pid(tid)的缓存,是为了减少::syscall(sys_gettid) 调用次数,提高获取tid的效率
        __thread char t_tidString[32]; //这是线程号tid的字符串表示形式
        __thread const char *t_threadName = "unknown";//const char *说明初始化的"unknown"内部内容不可变,但该指针可以指向别的字符串
        const bool sameType = boost::is_same<int, pid_t>::value; //pid_t是有符号32位整数,和int一样
        BOOST_STATIC_ASSERT(sameType);                           //编译时判定,通过!
    }                                                            // namespace CurrentThread

    namespace detail
    {

        pid_t gettid()
        {
            return static_cast<pid_t>(::syscall(SYS_gettid)); //系统调用获取一个真实的线程id唯一标识,即使不同进程中同一线程号的线程也可以区分
        }
        
        /*
            创建子进程以后,在子进程中重置t_cachedTid的值。
            此函数的目的就是给多线程后又多进程(fork)的代码,给子进程里的单线程取名“main”
        */
        void afterFork() //在子进程运行
        {
            muduo::CurrentThread::t_cachedTid = 0;//清零tid,为了执行CurrentThread::tid()中的cacheTid函数
            //为何在子线程中,线程名字也是main ?fork可能是在主线程或者子线程调用,fork得到的新进程
            //新进程只有一个执行序列(只有一个线程,调用fork的那个线程被继承下来)
            //事实上多进程和多线程做好不要混合编程
            muduo::CurrentThread::t_threadName = "main";
            CurrentThread::tid();   //重置t_cachedTid的值
            // 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); //pthread_atfork(void (*prepare)(void), void (*parent)(void),void (*child)(void));
                                                        //调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程
                                                        //会调用parent,子进程会调用child
            }
        };

        ThreadNameInitializer init;

        struct ThreadData                            //线程数据结构体放了线程真正要执行的函数,记为func_,线程id,线程name等信息
        {
            typedef muduo::Thread::ThreadFunc ThreadFunc;
            ThreadFunc func_;
            string name_;
            boost::weak_ptr<pid_t> wkTid_;

            ThreadData(const ThreadFunc &func, //data存放了线程真正要执行的函数,记为func_,线程id,线程name等信息
                       const string &name,
                       const boost::shared_ptr<pid_t> &tid)
                : func_(func),
                  name_(name),
                  wkTid_(tid)
            {
            }

            void runInThread()
            {
                pid_t tid = muduo::CurrentThread::tid(); //获取线程id(在CurrentThread.h文件)
				
                boost::shared_ptr<pid_t> ptid = wkTid_.lock();//if此时Thread对象已经销毁,lock得到的是一个空shared_ptr
                if (ptid)
                {
                    *ptid = tid;//共享对象的值改变为tid
                    ptid.reset(); //销毁shared_ptr<pid_t>ptid,其他共享对象引用计数-1
                }

                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
                }
            }
        };

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

    } // namespace detail
} // namespace muduo

using namespace muduo;

void CurrentThread::cacheTid() //存储tid
{
    if (t_cachedTid == 0)
    {
        t_cachedTid = detail::gettid();
        int n = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid); //tid写进数组
        assert(n == 6);
        (void)n;
    }
}

bool CurrentThread::isMainThread() //是不是主线程,tid==pid是主线程
{
    return tid() == ::getpid();
}

void CurrentThread::sleepUsec(int64_t usec) //线程挂起多久,默认0s
{
    struct timespec ts = {0, 0};
    ts.tv_sec = static_cast<time_t>(usec / Timestamp::kMicroSecondsPerSecond);
    ts.tv_nsec = static_cast<long>(usec % Timestamp::kMicroSecondsPerSecond * 1000);
    ::nanosleep(&ts, NULL);
}

AtomicInt32 Thread::numCreated_;

Thread::Thread(const ThreadFunc &func, const string &n) //Thread构造函数
    : started_(false),
      joined_(false),
      pthreadId_(0),
      tid_(new pid_t(0)),
      func_(func),
      name_(n)
{
    setDefaultName(); //线程名,以及static int numCreated_线程数+1
}

#ifdef __GXX_EXPERIMENTAL_CXX0X__
Thread::Thread(ThreadFunc &&func, const string &n)
    : started_(false),
      joined_(false),
      pthreadId_(0),
      tid_(new pid_t(0)),
      func_(std::move(func)),
      name_(n)
{
    setDefaultName();
}

#endif

Thread::~Thread()
{
    if (started_ && !joined_)
    {
        pthread_detach(pthreadId_);
    }
}

void Thread::setDefaultName()
{
    int num = numCreated_.incrementAndGet();//每创建一个函数,原子类就加1
    if (name_.empty())
    {
        char buf[32];
        snprintf(buf, sizeof buf, "Thread%d", num);//第几个线程"Thread%d"
        name_ = buf;
    }
}

void Thread::start() //启动线程函数的定义
{
    assert(!started_);
    started_ = true;
    // FIXME: move(func_)
    detail::ThreadData *data = new detail::ThreadData(func_, name_, tid_); //data存放了线程真正要执行的函数,记为func_,线程id,线程name等信息
    if (pthread_create(&pthreadId_, NULL, &detail::startThread, data))     //创建线程:线程函数为detail::startThread
    {
        started_ = false;                           //创建线程失败,设置标记线程未启动
        delete data;                                // or no delete?
        LOG_SYSFATAL << "Failed in pthread_create"; //输出日志
    }
}

int Thread::join()
{
    assert(started_); //必须是started状态且还没join
    assert(!joined_);
    joined_ = true;
    return pthread_join(pthreadId_, NULL);
}

/*
为什么使用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。



*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值