線程/進程間的通訊方式
—使用全局變量/共享內存
—使用thread中的lParam參數
—使用socket
—使用窗口和消息
—使用命名管道/匿名管道
—使用cmd參數
—使用environment變量
線程的啟動,退出和lParam參數通訊
VC: #include <windows.h> DWORD WINAPI ThreadProc(LPVOID lParam); DWORD dwThreadId; HANDLE hThread = CreateThread(NULL, 0, ThreadProc, lParam, 0, &dwThreadId); ::TerminateThread(hThread, 0) //殺死線程,強烈不推薦使用! ::WaitForSingleObject(hThread, INFINITE) //等待線程退出 ::CloseHandle(hThread); //不再關心該線程 ::GetExitCodeThread (); //獲取線程退出代碼
.Net: Using System.Threading; Static void ThreadProc(Object lParam) Object lParam = null; Thread th = new Thread(new ParameterizedThreadStart(ThreadProc)); th.Start(lParam); th.IsBackground = true; // 主循環結束後依靠.Net運行機制自動取消該線程 th.Join(); //等待線程退出 th.Abort(); //殺死線程
進程的啟動,退出,命令行和環境變量
VC: #include <windows.h> STARTUPINFO si = sizeof(STARTUPINFO) }; PROCESS_INFORMATION ps; Char* pFileName = …; Char* pArgs = …; LPVOID pEnv = NULL; BOOL bRet = CreateProcess(pFileName, pArgs, …, pEnv, NULL, &si, &ps); ::TerminateProcess(ps.hProcess, 0) //殺死進程,不推薦使用,但比TerminateThread強很多。 ::WaitForSingleObject(ps.hProcess, INFINITE) //等待進程退出 ::CloseHandle(ps.hProcess); // 不再關心該進程 ::GetExitCodeProcess(); //獲取進程退出代碼
.Net: Using System.Diagnotics; Process p = new Process(); p.StartInfo = new ProcessStartInfo(); p.StartInfo.FileName = …; p.StartInfo.Arguments = …; p.StartInfo.EnvironmentVariables = …; p.Start(); p.WaitForExit(); //等待進程退出 p.Kill(); //殺死進程 p.ExitCode // 獲取進程退出代碼
管道通訊
創建匿名管道(讀管道和寫管道) BOOL WINAPI CreatePipe( PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize); 銷毀匿名管道 BOOL WINAPI CloseHandle( HANDLE hPipe ); 管道通訊用例NPSample
共享內存(VC)
CreateFileMapping MapViewOfFile UnmapViewOfFile CloseHandle 共享內存實現用例ShmSample
線程/進程間的同步
有了以上進程通訊的方式,必然會產生同步問題。 同步的幾種方式: 臨界區CriticalSection --- 輕量級代碼關鍵段 互斥鎖Mutex --- 互斥鎖,只能被一個進程擁有 信號量Semaphore --- 信號燈, 事件Event ---- 一次性手動或自動事件 原子變量Atomic ---- 保證一個變量值唯一性 自旋鎖SpinLock ---- 用戶態自旋鎖,適合短代碼加鎖 都可以跨進程使用,但臨界區跨進程必須放於共享內存中。
CriticalSection
VC: #include <windows.h> CRITICAL_SECTION sec; InitializeCriticalSection(&sec); //初始化臨界區 InitializeCriticalSectionAndSpinCount(&sec, 2000); //自旋方式初始化臨界區 DeleteCriticalSection(&sec); //刪除臨界區 EnterCritcalSection(&sec); //進入臨界區 LeaveCriticalSection(&sec); //離開臨界區 TryEnterCriticalSection(&sec); //試圖進入臨界區,進入成功要離開,否則返回FALSE 注意:盡量使用類鎖(自動析構)如有性能或者防止死鎖需要,盡量不使用return,防止離開時忘記釋放鎖
.Net: String s; lock (s) // 鎖定對象s { ….; } System.Threading.Motitor.Enter(s); System.Threading.Motitor.Leave(s); //盡量加到finally塊裡面,避免拋出異常導致鎖未釋放
Mutex
VC: #include <windows.h> HANDLE mutex; mutex = CreateMutex(NULL,…); //初始化匿名互斥鎖 Mutex = CreateMutex(“123”, …); //初始化有名稱互斥鎖 Mutex = OpenMutex(“123”, …); //打開有名稱互斥鎖 CloseHandle(mutex); //關閉互斥鎖; WaitForSingleObject(mutex, waitTime); // 等待互斥鎖,第二個參數為等待時間 ReleaseMutex(mutex); //釋放互斥鎖; 注意:盡量使用類鎖(自動析構)如有性能或者防止死鎖需要,盡量不使用return,防止離開時忘記釋放鎖 注意:線程/進程退出時互斥鎖將自動被釋放,其他等待的線程/進程將獲得該鎖並返回WAIT_ABANDONED.
.Net: Using System.Threading; Mutex m = new Mutex(); Mutex m = new Mutex(“123”, …); //初始化有名稱互斥鎖 m.WaitOne(); // 等待互斥鎖 m.ReleaseMutex(); // 釋放互斥鎖,盡量加到finally塊裡面,避免拋出異常導致鎖未釋放
Semaphore
VC: #include <windows.h> HANDLE sem; sem = CreateSemaphore(NULL,…); //初始化匿名信號量並初始化初始信號數量 sem = CreateSemaphore(“123”, …); //初始化有名稱信號量 sem = OpenSemaphore(“123”, …); //打開有名稱信號量 CloseHandle(sem); //關閉信號量; WaitForSingleObject(sem, waitTime); // 等待信號量,第二個參數為等待時間,若成功,信號量計數-1 ReleaseSemaphore(sem, count); //將信號量計數增加count; 注意:盡量使用類鎖(自動析構)如有性能或者防止死鎖需要,盡量不使用return,防止離開時忘記釋放鎖 注意:線程/進程退出時信號量將不會被釋放,其他等待的線程/進程將依然會鎖住
.Net: Using System.Threading; Semaphore m = new Semaphore(); Semaphore m = new Semaphore(“123”, …); //初始化有名稱信號量 m.WaitOne(); // 等待信號量並將信號量數目-1 //
.Net: Using System.Threading; EventHandle m = new EventHandle (); EventHandle m = new EventHandle (“123”, …); //初始化有名稱信號量 m.WaitOne(); // 等待信號量並將信號量數目-1 m.Release (count); // 信號量計數增加count,盡量加到finally塊裡面,避免拋出異常導致鎖未釋放利用Event控制線程運行(VC)
看過很多線程代碼是這樣寫的 DWORD WINAPI ThreadProc(LPVOID param) { while (m_bRun) { DoWork(…); Sleep(1); } } Void Stop() { m_bRun = FALSE; ::WaitForSingleObject(…); }實際應該這樣優化: DWORD WINAPI ThreadProc(LPVOID param) { while (TRUE) { DWORD dwRet = WaitForSingleObject(hEvent, 1); if (dwRet == WAIT_OBJECT_0) break; DoWork(…); Sleep(1); } } Void Stop() { SetEvent(hEvent); ::WaitForSingleObject(…); }原子變量
—原子變量的原理
原子變量的原理是利用硬件支持鎖定某塊內存的功能實現,就算有多個CPU同時訪問該段內存,也只有一個能進入該內存,其他CPU將被鎖住。
由於原子變量並非對代碼段加鎖,而是對數據區加鎖,並且鎖的空間很小,因此一般只適合數量上的(引用計數)或者數值上的(個數,次數)的加鎖。
VC: InterlockedIncrement, InterlockedExchangeAdd, InterlockedDecrement, …
.Net: System.Threading.Interlocked類
自旋鎖
—自旋鎖是用戶態鎖,利用鎖定某塊內存的方式不斷讀取該塊內存數據來加鎖/解鎖,工作機理和原子變量類似,但要注意自旋鎖僅適合非單核CPU(單核在用戶態自旋是沒有意義的)和較短代碼段的鎖,若鎖的時間過長將引起大量的CPU耗損。
同步與死鎖
—死鎖原因
—忘記在某個地方釋放鎖
—使用TerminateThread/TerminateProcess導致鎖對象未釋放
—加鎖未按順序加鎖
—鎖太多,不知該如何加鎖
—
—每個鎖的鎖周期要短,不要對非關鍵代碼區域段加鎖
—每個鎖的目的要明確,不要一個鎖去鎖太多的對象和元素
—加鎖要按順序加鎖
—注意SendMessage類函數和回調函數的加鎖,確保在調用之前已經釋放了應該釋放的鎖