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中了。