Linux中,我們知道getpid(2) 可以獲取調用進程的pid,那么如何獲取一個線程的id呢?
可以用系統調用gettid(2)獲取內核中的線程id ,POSIX線程庫提供的pthread_self(3)方法獲取分配的線程id。C++11 std::thread的get_id()方法,封裝的也是POSIX pthread線程庫的線程id。
內核中的線程id,與pthread線程庫的線程id有何區別?
在Linux中,線程本質上是一個進程(實現),也就是說,通過系統調用gettid獲取的線程id跟進程id是一樣的。
glibc的Pthreads實現,把pthread_self返回值類型pthread_t用作一個結構體指針(類型為unsigned long),指向一個動態分配的內存,而且該內存是反復使用的。這也就是說,pthread_t的值很容易重復。Pthreads只能保證同一個進程內,同一時刻的各個線程id不同;但不能保證同一進程先后多個線程具有不同id,不能保證一台機器上多個進程間的id不同。
因此,pthread_t不適合作為程序中對線程的標識符。建議用gettid(2)系統調用返回值作為線程id,好處在於:
- 類型是pid_t,其值是一個小整數(最大值/proc/sys/kernel/pid_max,默認32768),便於log輸出;
- 表示內核的任務調度id,在/proc文件系統中,可以找到對應項:/proc/tid,或/prod/pid/task/tid;
- 在其他系統工具中容易定位到具體某個線程,如top(1)命令中,可以按線程列出任務,然后找出CPU使用率最高的線程id,再根據log判斷到底哪個線程在耗費CPU;
- 任何時刻都是全局唯一的,且由於Linux分配新pid采用遞增輪回辦法,短時間內啟動的多個線程也會具有不同的線程id;
- 0是非法值,因為操作系統的第一個進程init的pid是1,而gettid采用的線程tid本質上是進程pid,因此不能再為1。
示例
// 通過pthread線程庫獲取tid
void* threadFunc(void* arg)
{
pthread_t id = pthread_self(); // 獲取Pthreads線程id
printf("pthread id=%lx\n", id);
}
int main()
{
pthread_t th;
pthread_create(&th, NULL, threadFunc, NULL);
pthread_join(th, NULL);
return 0;
}
// C++11 std::thread通過get_id獲取tid
void threadFunc()
{
cout << th.get_id() << endl; // 獲取Pthreads線程id
}
int main()
{
std::thread th(threadFunc);
th.join();
return 0;
}
// 通過系統調用gettid獲取線程id
pid_t gettid()
{
return static_cast<pid_t>(::syscall(SYS_gettid));
}
利用thread local緩存線程id
由於系統調用會陷入內核,頻繁系統調用可能會影響系統性能,有沒有辦法可以避免這個問題?
答案是有的。考慮到線程id在線程創建后,並不會隨意改變,因此可以用每個線程自帶的thread local(__thread)變量來緩存其線程id值,初值設為0或負數即可。
// C++11 利用__thread緩存線程id
__thread int t_tid = 0;
// 用戶接口:獲取線程id
inline int tid()
{
if (t_tid == 0)
{
cacheTid();
}
return t_tid;
}
void cacheTid()
{
if (t_tid == 0)
{
t_tid = gettid();
}
}
// 通過系統調用gettid獲取線程id
pid_t gettid()
{
return static_cast<pid_t>(::syscall(SYS_gettid));
}
參考
[1]陳碩. Linux多線程服務端編程:使用muduo C++網絡庫[M]. 電子工業出版社, 2013.