core真的那麼難以追蹤嗎?
本週遇到了好幾個core
都很有典型性。在這裡和大家分享下。
相信有過Linux
編程經驗的人,肯定都遇到過。感覺周圍人很多對core
有天然的恐懼感,尤其對剛入行不久的同學來說。當然了,也有工作好幾年看到core也束手無策的。今天就分析一下,core
,其實大部分都是很容易解決的。如果一個core
很難以復現,那麼說明還是很複雜的,算是Corner case
,可能需要很長時間,腦子裡要有很好的運行時狀態才可以(閱讀源碼,學習的是邏輯;將源碼對應到運行時的狀態,分析一些狀態機的轉換,再去分析可能會發生的情況)。相信前幾篇文章會對這種Corner case
的分析與解決打下比較好的基礎。
相反,那種每次必現,或者復現比率非常高的case
,是非常容易解決的。
多線程必然出core?
如果是你新加入的代碼引入的core
,實際上非常容易解決的,簡單的對比一下修改的diff
,然後看一下是否有比較低級的錯誤。如果發現不了,看是否是多線程的問題?單線程如果沒有出core
,改成多線程就出core
,那麼就說明多線程競爭某些變量了:同時修改某些變量導致出問題。這個時候你可能第一反應會加鎖。我本人非常反感加鎖;即使你加鎖的粒度很小,作用域也夠小,但是隻要是加鎖,就代表有阻塞,就代表維護起來會很麻煩。這些共享變量真的那麼值得加鎖嗎?可否換成局部變量?如果他是一塊動態內存,為了調用某個接口時不要頻繁申請釋放內存(比如這個接口每秒幾千次的調用),那麼初始化時候申請一塊內存是絕對合理的:請把它設置為線程變量吧。每個線程初始化時候申請這塊內存。
當然了你如果實現的是一個框架或者架構調用的接口,這個接口要做到線程安全的。那麼看起來你並不能控制這個線程什麼時候啟動;線程數目會是多少個,那麼就沒有辦法了嗎?
實際上,方法有很多,比如,你可以在一個map
中維護一個“線程”變量的對應關係
__gnu_cxx::hash_map< pid_t, void *> thread_data_map;
void * thread_buffer;
std::map<pid_t, void *>::iterator it;
lock
it = thread_data_map.find(pid);
if (it == thread_data_map.end()) {
//init "thread data"
thread_data_map[pid] = create_buffer();
} else {
thread_buffer = it->second;
}
unlock
這裡不得不使用了一個鎖。實際上由於線程數是有限的,因此這個效率還好。我本週實現了一個qps
可以達到2000+
的在線應用,基本上鎖的代價在整個的call stack
中可以忽略不計。
當然了,比較好的框架可能會提供OnThreadInit
這種接口,那麼在這邊申請線程變量吧:
int pthread_setspecific (pthread_key_t key, const void *value);
在實現邏輯的函數獲取該變量即可:
void *pthread_getspecific (pthread_key_t key);
什麼時候要使用線程變量?看多線程下是否對該變量有寫操作,如果有就要申請線程變量(或者加鎖),否則必然出core
。
不要給自己埋下一個坑:
今天一個同學的core看起來是做了一個“優化”,節省了申請變量的時間。
void init() {
my_struct * some_var;
...
some_var->res = new some_res;
some_var->res->set_value1(some_common_value1);
some_var->res->set_value2(some_common_value2);
}
void * thread_func(my_struct * some_var) {
some_var->res->set_value3(value_3);
...
}
set_value3
就是一個出core
的原因。這個也是一個典型的多線程必然出core
的case
。實際上,res
沒必要提前申請吧。把它改成一個局部變量,性能幾乎沒有的損耗。當然了,如果這個資源很大,那麼就當成線程變量吧。
那麼如何分析core?
實際上,上述的場景出現的core
如果僅從core
本身,可能一時不好排查問題出在哪裡;而且core
的位置可能還不一樣,不可避免的出現替罪羊;比如調用第三方的模塊,實際上是自己的全局變量導致到了第三方的調用棧時出core
了。因此一定要排查自己的處理是否正確。
確定調用的接口是否線程安全;如果是你的同事,你得確定他說的是對的。就像今天又另外的一個core
,調用者堅稱他寫的是線程安全的,彷彿不是線程安全的就像是代碼寫的不好似得;最後排查出他寫的代碼不是線程安全時,他問你什麼是線程安全的。
那麼回答一下,如何分析core?
首先了解清楚core
出現的應用場景,比如長句子處理會有core
;多線程處理會有core
;特殊字符會有core
;這個QA
會給你一個比較清楚的說明。然後通過core
的call stack
去大概確定大概的源碼位置。
源碼面前,了無祕密。
你讀源碼,它展現的是邏輯;但是你腦中,要有個runtime
的運行時調用棧,多線程的調度;天馬行空。
當然了,還是解決不了,開始從core
得到更詳細的信息吧!看看調用棧的參數是什麼,切換一個線程看看其他的幾個線程的frame
是什麼。
還解決不了?
那麼它是Corner case
,只要把應用在core
後立即啟動就行了。記得EMC
有個bug
的選項是unable to root cause
, 形容的很貼切。同時不要忘記自我安慰:碼的碼多了,肯定有bug
,誰能保證服務100%
呢?