pid_t gettid()
{
return static_cast<pid_t>(::syscall(SYS_gettid));
}
gettid函数用来获取线程的ID的,但是获取的不是pthread_self返回的pthread_t类型的线程ID,而是pid_t。
POSIX线程ID与Linux专有的系统调用gettid()所返回的线程ID并不相同。POSIX线程ID由线程库实现来负责分配和维护。gettid()返回的线程ID是一个由内核分配的数字,类似于进程ID。
根据作者的说法,之所以不用pthread_self返回的pthread_t类型的ID作为线程的唯一标识的原因是:
- 无法打印输出pthread_t,因为不知道其确切类型。也就没法在日志中用它表示当前线程的id
- 无法比较pthread_t的大小或计算其hash值,因此无法用作关联容器的key
- 无法定义一个非法的pthread_t值,用来表示绝对不可能存在的线程id,因此MutexLock class没法有效判断当前线程是否已经持有本锁
- pthread_t值只在进程内有意义,与操作系统的任务调度之间无法建立有效关联。比方说在/proc文件系统找不到pthread_t对应的task。
- glibc的Pthreads实现实际上把pthread_t用作一个结构体指针(它的类型是unsigned long),指向一块动态分配的内存,而且这块内存是反复使用的。这就造成pthread_t的值很容易重复。Pthreads只保证同一进程之内,同一时刻的各个线程的id不同;不能保证同一进程先后多个线程具有不同的id,更不要说一台机器上多个进程之间的id唯一性了
不过有一点在《Linux/UNIX系统编程手册》书中说:在Linux的线程实现中,线程ID在所有进程中都是唯一的。
当然,系统库并不提供gettid函数,只能通过syscall(SYS_gettid)自己实现。
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;
这部分代码中,使用了pthread_atfork函数,该函数之前也没用过,所以这里也做个简单测试。
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
该函数在需要调用fork创建子进程时使用,prepare参数是在fork创建子进程前由父进程调用。fork后父进程会调用parent,子进程会调用child。所以该函数有一个特别重要的功能是在多线程程序中创建子进程避免子进程死锁。例如下面的程序
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void child_process()
{
pthread_mutex_lock(&mutex);
printf("I am child\n");
pthread_mutex_unlock(&mutex);
}
void *func(void *arg)
{
pthread_mutex_lock(&mutex);
sleep(3);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, func, NULL);
sleep(1);
pid_t pid = fork();
if (pid == 0)
{
child_process();
exit(EXIT_SUCCESS);
}
else if (pid < 0)
{
exit(EXIT_FAILURE);
}
printf("I am parent\n");
getchar();
}
如果编译运行该程序,那么child函数中的打印永远不会发生,因为在fork前,mutex是锁住的状态,fork后,子进程复制了mutex,所以mutex在子进程中是锁住的状态,pthread_mutex_lock会一直阻塞。即使父进程解锁也没用,因为子进程中的锁和父进程中的锁已经不是一个了。这时候pthread_atfork就排上用场了。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void child_process()
{
pthread_mutex_lock(&mutex);
printf("I am child\n");
pthread_mutex_unlock(&mutex);
}
void prepare()
{
pthread_mutex_lock(&mutex);
}
void parent()
{
pthread_mutex_unlock(&mutex);
}
void child()
{
pthread_mutex_unlock(&mutex);
}
void *func(void *arg)
{
pthread_mutex_lock(&mutex);
sleep(3);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main()
{
pthread_atfork(prepare, parent, child);
pthread_t tid;
pthread_create(&tid, NULL, func, NULL);
sleep(1);
pid_t pid = fork();
if (pid == 0)
{
child_process();
exit(EXIT_SUCCESS);
}
else if (pid < 0)
{
exit(EXIT_FAILURE);
}
printf("I am parent\n");
getchar();
}
这样修改后,子进程就可以正常打印了。因为在fork前,父进程会调用prepare函数,该函数会执行加锁,所以fork的时候,父子进程都是加锁状态,而fork后,父进程调用parent解锁,子进程调用child解锁。最终父子进程都是无锁状态。子进程就可以正常加锁运行了。
在muduo库中,只使用了pthread_fork的最后一个参数,也就是fork后子进程要执行afterFork。目的是为了在子进程中初始化子进程的t_cachedTid和t_threadName,否则这两个值就是调用fork的那个线程的值。
ThreadNameInitializer init;使用全局变量初始化主线程信息,在muduo库中,好多地方都是用了全局变量初始化全局信息。特别是EventLoop.cc文件中的IgnoreSigPipe类,用全局变量的初始化设置SIGPIPE信号。
struct ThreadData
{
typedef muduo::Thread::ThreadFunc ThreadFunc;
ThreadFunc func_;
string name_;
pid_t* tid_;
CountDownLatch* latch_;
ThreadData(ThreadFunc func,
const string& name,
pid_t* tid,
CountDownLatch* latch)
: func_(std::move(func)),
name_(name),
tid_(tid),
latch_(latch)
{ }
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
}
}
};
void* startThread(void* obj)
{
ThreadData* data = static_cast<ThreadData*>(obj);
data->runInThread();
delete data;
return NULL;
}
} // namespace detail
void CurrentThread::cacheTid()
{
if (t_cachedTid == 0)
{
t_cachedTid = detail::gettid();
t_tidStringLength = snprintf(t_tidString, sizeof t_tidString, "%5d ", t_cachedTid);
}
}
bool CurrentThread::isMainThread()
{
return tid() == ::getpid();
}
void CurrentThread::sleepUsec(int64_t usec)
{
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(ThreadFunc func, const string& n)
: started_(false),
joined_(false),
pthreadId_(0),
tid_(0),
func_(std::move(func)),
name_(n),
latch_(1)
{
setDefaultName();
}
Thread::~Thread()
{
if (started_ && !joined_)
{
pthread_detach(pthreadId_);
}
}
void Thread::setDefaultName()
{
int num = numCreated_.incrementAndGet();
if (name_.empty())
{
char buf[32];
snprintf(buf, sizeof buf, "Thread%d", num);
name_ = buf;
}
}
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();
assert(tid_ > 0);
}
}
int Thread::join()
{
assert(started_);
assert(!joined_);
joined_ = true;
return pthread_join(pthreadId_, NULL);
}
这部分内容虽然不少,但是都很简单,基本上就是封装的pthread线程库。关于CurrentThread的部分之后再说,其他的内容在之前的博客中差不多都有说明,像AtomicInt32、prctl、CountDownLatch,所以这里就不说了。