(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 上的宏 WINAPI 和 CALLBACK 這兩個宏的定義都是 __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_t 和 DWORD 類型都是一個 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::thread 的 get_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 WaitForSingleObject 或 WaitForMultipleObjects 函數(shù)岂座,前者為等待一個線程結(jié)束,后者為等待多個線程結(jié)束杭措,可選擇控制等待時間费什,
? ? ? ? ? ? ? ? ? ? ? ? ?? DWORD? WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
? ? ? ? ? ? ? ? ? ? ? ? ? //參數(shù)hHandle是需要等待的對象的句柄,等待線程退出手素,傳入線程句柄鸳址。參數(shù)dwMilliseconds是需要等待的毫秒數(shù)赘那,如果使用INFINITE宏,則表示無限等待下去,此時會掛起當前等待線程氯质,直到等待的線程退出后募舟,等待的線程才會被喚醒。
? ? ? ? ? ? ?? C++11提供函數(shù)(windows和linux均使用):std::thread 的 join 方法闻察,為了防止等待線程已經(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ū)別。
? ? ? ? ? ? ? ? ? ? ?