变量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。
*/