多線程編程精髓(一)

(1)線程的基本概念和常見問題: 每個進程都有自己的獨立進程地址空間和上下文堆棧奶镶,進程中實際執(zhí)行單位為線程羹奉,每個進程至少有一個線程-主線程,線程是由操作系統(tǒng)安排調(diào)度的最小運行單元笔横,進程中的線程可分為主線程和工作線程,實際使用中避免主線程退出咐吼,線程是獨立運行的最小單元吹缔,擁有獨立的上下文堆棧,正常來說一個線程奔潰不會影響其他線程锯茄,但會導致進程退出厢塘,從而其他線程也無法運行。

(2)線程的創(chuàng)建和使用:不同操作系統(tǒng)使用不同的API函數(shù)肌幽,

? ? ? ? ?? linux創(chuàng)建線程 :int? pthread_create(pthread_t *thread,const pthread_attr_t * attr,void* (*start_routine) (void*),void* arg); //thread 線程ID,attr 線程屬性晚碾,設置為NULL,start_routine 線程調(diào)用函數(shù),arg 線程傳入?yún)?shù);喂急,返回值為0 表示創(chuàng)建成功格嘁;

? ? ? ? ? ? windows創(chuàng)建線程:HANDLECreateThread( LPSECURITY_ATTRIBUTES? lpThreadAttributes,? SIZE_T? dwStackSize,? LPTHREAD_START_ROUTINE? lpStartAddress, LPVOID ? lpParameter,DWORD ? dwCreationFlags,? LPDWORD ?? lpThreadId);? // lpThreadAttributes 線程安全屬性 設置NULL, dwStackSize 線程椑纫疲空間糕簿,設置0表示默認大小,lpStartAddress 線程函數(shù)指針狡孔,dwCreationFlags 32位無符號整形懂诗,設置為0表示線程創(chuàng)建后立即啟動,pThreadId 線程創(chuàng)建成功返回的ID苗膝,返回為內(nèi)核句柄的索引值殃恒,為NULL表示失敗荚醒;

? ? ? ? ?? 注意:linux的線程函數(shù)調(diào)用方式是__cedel芋类,這是C/C++ 中定義函數(shù)時默認的調(diào)用方式隆嗅,而windows上調(diào)用方式createThread 定義線程函數(shù)時必須使用 __stdcall 調(diào)用方式界阁,必須顯示聲明函數(shù),如?DWORD __stdcall threadfunc(LPVOID lpThreadParameter) 胖喳,Windows 上的宏 WINAPICALLBACK 這兩個宏的定義都是 __stdcall泡躯,因此也可以寫成:

? ? ? ? ? ? //寫法1? DWORD WINAPI threadfunc(LPVOID lpThreadParameter);

? ? ? ? ?? //寫法2? DWORD CALLBACK threadfunc(LPVOID lpThreadParameter);

? ? ? ? ? ? windows C函數(shù)庫(對比上述,推薦使用):uintptr_t? _beginthreadex(? void *security, ? unsigned stack_size,? unsigned ( __stdcall *start_address )( void * ), ? void *arglist, unsigned initflag, ? unsigned *thrdaddr );? //函數(shù)簽名與上述一樣丽焊;

? ? ? ? ? ?? C11提供std::thread: 創(chuàng)建線程t1 std::threadt1(threadproc1);,創(chuàng)建線程t2? ? std::threadt2(threadproc2,1,2);? //對比上述的幾種方式的固定格式函數(shù)较剃,此方式便捷,函數(shù)簽名更加多樣化技健,但必須保證線程對象在線程運行期間有效不被銷毀写穴。

(3)排查linux進程占用CPU過高的方法:使用ps命令查看進程的ID,top -H 命令也可以顯示每個進程的各個線程的運行狀態(tài)雌贱。然后使用pstack命令查看進程中每個線程調(diào)用的堆棧啊送,注意pstack是gdb調(diào)試器支持的偿短,命令查看程序必須攜帶調(diào)試權(quán)限,而且用戶必須有查看權(quán)限馋没,對于pstack輸出的各個線程中昔逗,對照源碼邏輯排查進行修改優(yōu)化,提高CPU資源的利用率篷朵。

(4)線程ID的用途以及原理:線程ID是用于標識一個線程的整形數(shù)值勾怒,熟練獲取能幫助日志寫入和后續(xù)問題排查。

? ? ? ? ?? windows環(huán)境下可以在創(chuàng)建線程是直接獲得声旺,或者使用以下函數(shù):

? ? ? ? ? ? ? ? ? ? ? DWORD GetCurrentThreadId();?

? ? ? ? ? ? ? ? ? ?? //注意 pthread_tDWORD 類型都是一個 32 位無符號整型值

? ? ? ? ?? linux環(huán)境下有三種方式笔链,在創(chuàng)建線程時獲得、在需要獲取 ID 的線程中調(diào)用 pthread_self() 函數(shù)獲取艾少、?通過系統(tǒng)調(diào)用獲取線程 ID:

? ? ? ? ? ? ? ? ? ? 1.? #include<pthread.h>

? ? ? ? ? ? ? ? ? ? ?? pthread_t tid;

? ? ? ? ? ? ? ? ? ? ?? pthread_create(&tid, NULL, thread_proc, NULL);

? ? ? ? ? ? ? ? ?? 2 .#include<pthread.h>?

? ? ? ? ? ? ? ? ? ? ?? pthread_t tid = pthread_self();

? ? ? ? ? ? ? ? ? ? 3.#include<sys/syscall.h>

? ? ? ? ? ? ? ? ? ? ?? #include<unistd.h>

? ? ? ? ? ? ? ? ? ? ?? int tid = syscall(SYS_gettid);

? ? ? ? ? ? ? ? ? ? ? //注意方法一方法二獲取的線程 ID 結(jié)果是一樣的(轉(zhuǎn)成16進制與pstack命令查詢的線程ID一致)卡乾,都是一個很大的數(shù)字,表示內(nèi)存地址缚够,全局并不唯一幔妨,?而方法三獲取的線程 ID 是系統(tǒng)范圍內(nèi)全局唯一的,一般是一個不會太大的整數(shù)谍椅,這個數(shù)字也是就是所謂的 LWP (Light Weight Process误堡,輕量級進程),早期的 Linux 系統(tǒng)的線程是通過進程來實現(xiàn)的雏吭,這種線程被稱為輕量級線程)的 ID锁施。

? ? ? ? ? ? ?? C++11獲取線程ID用兩種方式: std::this_thread 類的 get_id 獲取當前線程的 id(類靜態(tài)方法)、std::threadget_id 獲取指定線程的 ID(類實例化方法):

? ? ? ? ? ? ? ? ? ? ? ?? 1. std::thread? t(worker_thread_func);

? ? ? ? ? ? ? ? ? ? ? ? ? ?? std::thread::id worker_thread_id = t.get_id();? ? //獲取線程t的ID ? ?

? ? ? ? ? ? ? ? ? ? ? ?? 2.? std::thread::id main_thread_id = std::this_thread::get_id();? //獲取主線程的線程 ID

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? std::ostringstream oss;?? //先將std::thread::id轉(zhuǎn)換成std::ostringstream對象? ?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? oss << main_thread_id;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? std::string str = oss.str();? //再將std::ostringstream對象轉(zhuǎn)換成std::string?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? int threadid = atol(str.c_str());//最后將 std::string 轉(zhuǎn)換成整型值? ?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? std::cout << "main thread id: " << threadid << std::endl;

(5)如何等待線程結(jié)束:一個線程需要等待另外一個線程執(zhí)行完畢退出后再執(zhí)行杖们,windows和linux都有提供對應的操作系統(tǒng)API:

? ? ? ? ? ? ? ?? linux環(huán)境下:使用 pthread_join 函數(shù)悉抵,用來等待某線程的退出并接收它的返回值。這種操作被稱為連接(joining)摘完,

? ? ? ? ? ? ? ? ? ? ? ? ? ? int? pthread_join(pthread_tthread,void** retval);

? ? ? ? ? ? ? ? ? ? ? ? ?? //參數(shù)thread姥饰,需要等待的線程 id,參數(shù)retval,輸出參數(shù)孝治,用于接收等待退出的線程的退出碼(Exit Code)列粪,線程退出碼可以通過調(diào)用pthread_exit退出線程時指定,也可以在線程函數(shù)中通過return語句返回,可設置為NULL,此時線程會被掛起直到目前線程退出再被喚醒谈飒。

? ? ? ? ? ? ?? windows環(huán)境下:使用 API WaitForSingleObjectWaitForMultipleObjects 函數(shù)岂座,前者為等待一個線程結(jié)束,后者為等待多個線程結(jié)束杭措,可選擇控制等待時間费什,

? ? ? ? ? ? ? ? ? ? ? ? ?? DWORD? WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

? ? ? ? ? ? ? ? ? ? ? ? ? //參數(shù)hHandle是需要等待的對象的句柄,等待線程退出手素,傳入線程句柄鸳址。參數(shù)dwMilliseconds是需要等待的毫秒數(shù)赘那,如果使用INFINITE宏,則表示無限等待下去,此時會掛起當前等待線程氯质,直到等待的線程退出后募舟,等待的線程才會被喚醒。

? ? ? ? ? ? ?? C++11提供函數(shù)(windows和linux均使用):std::threadjoin 方法闻察,為了防止等待線程已經(jīng)退出出現(xiàn)奔潰拱礁,調(diào)用之前需判斷是否可join,調(diào)用joinable 方法辕漂,

? ? ? ? ? ? ? ? ? ? ? ? ?? std::thread? t(FileThreadFunc);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (t.joinable())

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? t.join();

(6)線程函數(shù)傳遞C++類實例指針慣用法:除了C++11線程庫提供的std::thread對線程函數(shù)簽名沒有特殊的要求之外呢灶,其余windows和linux對于函數(shù)簽名都必須指定格式,此時線程函數(shù)

必須是靜態(tài)方法钉嘹,原因是C++編譯器將函數(shù)翻譯成全局函數(shù)時會將類的實例對象地址(也就是 this 指針)作為第一個參數(shù)傳遞給該方法鸯乃,比如void *threadFunc(void* arg) 翻譯之后就變成

了void* threadFunc(Thread*this,void* arg); 這樣就不符合函數(shù)簽名的要求了,如果是類的靜態(tài)方法就沒有辦法訪問類的實例方法了跋涣,因此實際開發(fā)中創(chuàng)建線程時將當前對象的地址(this

?指針)傳遞給線程函數(shù)缨睡,然后在線程函數(shù)中,將該指針轉(zhuǎn)換成原來的類實例陈辱,再通過這個實例就可以訪問類的所有方法了奖年,實際上C++11的std::thread創(chuàng)建時要求傳入this對象也是一樣

的道理。

(7)整型變量的原子操作:多線程同時操作某個資源沛贪,以整型變量為例陋守,同時對資源進行讀和寫,需要采用措施保護資源利赋,避免沖突水评。舉例說明:

? ? ? ? 把一個變量的值賦值給另外一個變量,或者把一個表達式的值賦值給另外一個變量媚送,如int a = b;從 C/C++ 語法的級別來看中燥,這也是一條語句,是原子的季希;但是從實際執(zhí)行的二進制

指令來看褪那,由于現(xiàn)代計算機 CPU 架構(gòu)體系的限制幽纷,數(shù)據(jù)不可以直接從內(nèi)存搬運到另外一塊內(nèi)存式塌,必須借助寄存器中斷,這條語句一般對應兩條計算機指令友浸,即將變量b的值搬運到某個寄

存器如eax)中峰尝,再從該寄存器搬運到變量a的內(nèi)存地址:move ax,dword ptr [b]? mov dword ptr [a], eax既然是兩條指令,那么多個線程在執(zhí)行這兩條指令時收恢,某個線程可能會在第一條指

令執(zhí)行完畢后被剝奪 CPU 時間片武学,切換到另外一個線程而產(chǎn)生不確定的情況祭往。

? ? ? ? ? windows下提供整型原子操作API(Windows.h頭文件),僅列出了與 32 位(bit)整型相關(guān)的 API 函數(shù)火窒,Windows 還提供了對 8 位硼补、16 位以及 64 位的整型變量進行原子操作的 API:

? ? ? ? ? 函數(shù)名 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 函數(shù)說明

? ? ? ? ? InterlockedIncrement ? ? ? ? ? ? ? ? 將 32 位整型變量自增 1

? ? ? ?? InterlockedDecrement ? ? ? ? ? ? ? ? 將 32 位整型變量自減 1

? ? ? ?? InterlockedExchangeAdd ? ? ? ? ? 將 32 位整型值增加 n (n 可以是負值)

? ? ? ?? InterlockedXor ? ? ? ? ? ? ? ? ? ? ? ? ? ? 將 32 位整型值與 n 進行異或操作

? ? ? ?? InterlockedCompareExchange ?? 將 32 位整型值與 n1 進行比較,如果相等熏矿,則替換成 n2


? ? ? ? ? C++11提供的std::atomic模板類型? template<class T? >struct atomic已骇,支持傳入具體的整型類型(如 bool、char票编、short褪储、int、uint 等)對模板進行實例化慧域,比如 std::atomic<int>?

value; ? value = 99; 還有大量方法可支持查詢使用鲤竹,注意在linux和windows環(huán)境不同用法的語法區(qū)別。



? ? ? ? ? ? ? ? ? ? ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昔榴,一起剝皮案震驚了整個濱河市辛藻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌互订,老刑警劉巖揩尸,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異屁奏,居然都是意外死亡岩榆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進店門坟瓢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來勇边,“玉大人,你說我怎么就攤上這事折联×0” “怎么了?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵诚镰,是天一觀的道長奕坟。 經(jīng)常有香客問我,道長清笨,這世上最難降的妖魔是什么月杉? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮抠艾,結(jié)果婚禮上苛萎,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好腌歉,可當我...
    茶點故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布蛙酪。 她就那樣靜靜地躺著,像睡著了一般翘盖。 火紅的嫁衣襯著肌膚如雪桂塞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天馍驯,我揣著相機與錄音藐俺,去河邊找鬼。 笑死泥彤,一個胖子當著我的面吹牛欲芹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吟吝,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼菱父,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剑逃?” 一聲冷哼從身側(cè)響起浙宜,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛹磺,沒想到半個月后粟瞬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馍佑,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡详炬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了咧党。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俗或。...
    茶點故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡市怎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辛慰,到底是詐尸還是另有隱情区匠,我是刑警寧澤,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布帅腌,位于F島的核電站驰弄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏速客。R本人自食惡果不足惜戚篙,卻給世界環(huán)境...
    茶點故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挽封。 院中可真熱鬧已球,春花似錦、人聲如沸辅愿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽点待。三九已至阔蛉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間癞埠,已是汗流浹背状原。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留苗踪,地道東北人颠区。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像通铲,于是被迫代替她去往敵國和親毕莱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容