410 likes | 616 Views
Thread Synchronization with Kernel object. 井民全. 為何要使用 Kernel Object 來做同步 ?. User Mode Synchronization 的好處 速度快 User Mode 的限制是 只能處理單一 Process 內的 threads 之間同步 在單一 CPU 情況下 , 你不能設定等待時間 Interlocked 家族函式不能讓 thread 進入 wait 狀態. 不能處理 其他 Process 之間的同步. EnerCriticalSection(…) 飢餓狀態.
E N D
為何要使用 Kernel Object 來做同步? • User Mode Synchronization的好處 • 速度快 • User Mode 的限制是 • 只能處理單一 Process內的 threads之間同步 • 在單一CPU情況下, 你不能設定等待時間 • Interlocked 家族函式不能讓 thread 進入 wait 狀態 不能處理 其他 Process之間的同步 EnerCriticalSection(…) 飢餓狀態 讓 CPU 空轉
Kernel Object 的性質 • 每一個核心物件都有紀錄狀態 • Process object 被建立 process handle non-signal • 當 Process 終止時 process handle signal • 希望等待某個 Process terminated, 怎麼辦? • 只要檢查該Process是否 signal即可 等待的過程中,你的 thread 不會佔住 CPU time
最多只能64 (MAXIMUM_ WAIT_OBJECTS) 有哪些 Wait functions 呢? DWORD WaitForSingleObject( HANDLE hObject, DWORD dwMilliseconds); 等待某一個 kernel object signal • 等待時間限制: • 一般 INFINITE OS 會幫你監控 Kernel Object 的狀態, 在 Signal 之前. 你的 Thread 不會被安排進入排程 DWORD WaitForMultipleObjects( DWORD dwCount, CONST HANDLE* phObjects, BOOL fWaitAll, DWORD dwMilliseconds); 數量 Object 陣列 把要等待的 Object 放到CONST HANDLE陣列中
DWORD dw = WaitForSingleObject(hProcess, 5000); switch (dw) { case WAIT_OBJECT_0: break; case WAIT_TIMEOUT: break; case WAIT_FAILED: // Bad call to function (invalid handle?) break; } 等待的 Process terminate 等待的 Process 並沒有在 5000 millisecond內 terminate 等待時間設定為 INFINITE是危險的 • 若等待的 Object 永遠不會 Signal • 則會造成 dead-lock(永遠不會醒來) 比較好的策略是這樣
如果我要等待多個物件怎麼辦? • 使用 WaitForMultiObject(…) • 最多只能等待 64個物件 DWORD WaitForMultipleObjects( DWORD dwCount, CONST HANDLE* phObjects, BOOL fWaitAll, DWORD dwMilliseconds); TRUE 必須等待所有的 object 都 signal FALSE 只要有一個 object signal 即可
關鍵片段 由 WAIT_OBJECT_0 決定 如何得知是那一個 Object Signal ? 一個簡單的範例 HANDLE h[3]; h[0] = hProcess1; h[1] = hProcess2; h[2] = hProcess3; DWORD dw = WaitForMultipleObject(3,h, FALSE, 5000); switch (dw) { case WAIT_TIMEOUT: break; case WAIT_FAILED: // Bad call to function (invalid handle?) break; case WAIT_OBJECT_0 + 0: break; case WAIT_OBJECT_0 + 1: break; case WAIT_OBJECT_0 + 2: break; } 現在設定為只要一個 Object signal 就跳離 h[0] terminated h[1] terminated h[2] terminated
有哪些 Kernel Object 支援 signal? • Processes • Thread • Jobs • Files • Console input • File change notifications • Events • Waitable timers • Semaphores • Mutexes
現在來討論 多個 Threads 同時 Wait 一個 object 的情況 • 當成功的呼叫 WaitForSingleObject 等functions • 通常會改變 kernel object 的狀態 Side effect • 不同的object 有不同的 Side effect • Auto-reset object • 建立 non-signal • Terminated signal 通知其他等待的thread • 當有人成功的呼叫 Wait function 自動設回 non-signal 有些 Wait function不會改變 這些 object 的狀態 例如: Thread , Process Object 就沒有 Side effect
考慮下面的情況 • 若有兩條threads, 同時執行下面的程式碼 HANDLE h[2]; h[0] = hAutoResetEvent1; h[1] = hAutoResetEvent2; WaitForMultipleObjects(2, h, TRUE, INFINITE); 等待的是兩個 Auto-reset objects 兩個 Object 必須都是 signal 才會跳出來
統一設定 Signal 統一設定為 non-Signal BOOL SetEvent (HANDLE hEvent); BOOL ResetEvent(HANDLE hEvent); 利用 Event Object 作行程間的同步 • 策略是 • 主程式建立 Event Object • Child Process等待該 Event Signal後才做事 TRUE: 設定為必須手動,才會 Reset False: Auto-reset 建立一個 Global 等級 Event HANDLE CreateEvent( PSECURITY_ATTRIBUTES psa, BOOL fManualReset, BOOL fInitialState, PCTSTR pszName); TRUE: 剛建立時,這個 Object 就是 signal 狀態 False: non-signal 狀態 決定你的 childe process 是否可以存取這個 Object HANDLE OpenEvent( DWORD fdwAccess, BOOL fInherit, PCTSTR pszName); 你想要對該 Object 的動作 目前我們使用 SYNCHRONIZE 另外的 Process 則用 OpenEvent 開啟
Event 名稱為 Fired 設定為手動reset HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,"Fired"); if(hEvent == NULL) ErrorExit( "建立 Event 發生錯誤" ); 初值為 non-signal 只希望用在同步上 HANDLE hEvent=OpenEvent(SYNCHRONIZE, FALSE,"Fired"); 不希望讓其他 衍生process 使用 WaitForSingleObject(hEvent,INFINITE); 簡單同步範例 建立一個 Global 等級 Event 建立兩個 ChildProcess 等待工頭的命令 CreateEventDemo
1 兩個 Processes 同時收到 Signal 2 只有一個 Process 收到 Signal 補充 設定為手動reset HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,"Fired"); if(hEvent == NULL) ErrorExit( "建立 Event 發生錯誤" ); • 如果: Manual 設定為 TRUE, 則當有人 • SetEvent hEvent 時, hEvent 會 Signal • 造成 Process A 與 Process B 成功的 wait 到這項訊號 • 如果: Manual 設定為 FALSE, 則當有人 • SetEvent hEvent 時, hEvent 會 Signal • 造成 Process A 或 Process B 只有一個會成功的 wait到這項訊號 Process A Process B 因為其中一個 成功 wait hEvent 後, hEvent 會自動設定為 non-signal 造成另一個 process 繼續的等待
我們現在討論 Waitable Timer來做同步化的動作 • 固定時間啟動工作 • 週期的啟動工作
最簡單的範例: CreateWaitableTimer 主控程式 我們的Timer 名稱 HANDLE hTimer; hTimer =CreateWaitableTimer(NULL, FALSE, "MyControlTimer"); const int nTimerUnitsPerSecond = 10000000; LARGE_INTEGER li; li.QuadPart= -1 * (10 * nTimerUnitsPerSecond); SetWaitableTimer(hTimer,&li, 0, \ // period =0 NULL, NULL, \ FALSE); Auto-reset // 1 秒 = 10,000,000 (100 nanosecond) 負數表示 相對啟動時間 設定 10 秒鐘後,這個 object 會 signal complete routine 參數 不使用 complete routine TRUE: 當 signal 後, 將系統從 suspended power conservation restore 回來
最簡單的範例: OpenWaitableTimer 當 Timer Signal 才會執行的 Process HANDLE hTimer=NULL; while(hTimer==NULL) hTimer=OpenWaitableTimer(SYNCHRONIZE, FALSE, "MyControlTimer"); // 當 Timer signal才能執行工作 WaitForSingleObject(hTimer,INFINITE); 我們等待的Timer 名稱 這個Timer不希望能被繼承 這個Timer指能用在 wait function 注意: Timer 一定要先建立,也就是說,主控程式要先執行
最簡單的範例: 程式架構 設定 Timer 啟動時間 五秒後,啟動 拉動 slider 功能 啟動時,就直接 Create Timer 物件 WaitableTimerDemo
那麼要如何設定時間呢? 我希望能在西元2005年1月1日下午1時整,然後每隔6小時啟動一次信號 我該如何指定啟動時間? 週期? SetWaitableTimer(hTimer,&li, 0, NULL, NULL, FALSE);
我們使用 SYSTEMTIME 指定年月日 西元2005年1月1日下午1時整 SYSTEMTIME st; st.wYear = 2005; // Year st.wMonth = 1; // January st.wDayOfWeek = 0; // Ignored st.wDay = 1; // The first of the month st.wHour = 13; // 1PM st.wMinute = 0; // 0 minutes into the hour st.wSecond = 0; // 0 seconds into the minute st.wMilliseconds = 0; // 0 milliseconds into the second
將 System Time 轉成適當的參數 FILETIME(從 1601年 1 月 1 號 12:00 AM 到現在經過多少100 ns單位) 將SystemTime轉成以100 ns為單位 Local FileTime 1 FILETIME ftLocal, ftUTC; SystemTimeToFileTime(&st, &ftLocal); 前面算出來的 SystemTime 2 將 Local FileTime Coordinated Universal Time(英國格林威治當地目前時間) LocalFileTimeToFileTime(&ftLocal, &ftUTC); 主要是處理位址對齊的問題 LARGE_INTEGER 是一個 union 可以把 LowPart 與 HiPart 組合成一個 64-bit 大數 3 FileTime LARGE_INTEGER 表示式 LARGE_INTEGER liUTC; liUTC.LowPart = ftUTC.dwLowDateTime; liUTC.HighPart = ftUTC.dwHighDateTime; 設定時間 週期為 6 hours = 6*60*60*1000 milliseconds SetWaitableTimer(hTimer,& liUTC, 6 * 60 * 60 * 1000, NULL, NULL, FALSE); 要求使用 UTC-Based absolute time
Windows 中的 Time 觀念 人看的懂得 機器看的 本地時間 (相對於 UTC) MS-DOS 16-bit 應用程式記錄時間的格式 若使用 GetFileTime 讀取 MS-DOS 應用程式建立的檔案時間, 則系統會自動幫你把 MS-DOS Time 轉換成 UTC-based FileTime 傳回來 因為 System Time 可以往前或往後 更改, 所以若要記錄計算時間可以使用 Windows Time
補充 FileTime 基本觀念 • The system records file times whenever applications create, access, and write to files • 例外: • NT FAT create time , write time and access time 使用不同的 time resolution • NTFS access time 的 resolution = 1 hour • 如何取得指定檔案的 file time ? • 呼叫 GetFileTime函式: 他會將資料複製到 FILETIME 的結構中 • FindFirstFile , FindNextFile: 會將資料複製到 WIN32_FIND_DATA 結構中
補充 FileTime 基本觀念 • 設定 file time? • 呼叫 SetFileTime 函式 • 比較不同檔案的 time? • 呼叫 CompareFileTime 函式 • 注意事項: 呼叫前,應該使用 SystemTimeToFileTime 先把日期等 SystemTime 資訊轉成 FileTime * 當然, 你也可以呼叫 FileTimeToSystemTime 將 FileTime 資料轉成看的懂得 SystemFile 然後把 FileTime 餵給 SetFileTime 傳回 0 表示時間一樣
補充 日光節約時間概念(Daylight Saving Time ) • 也叫做夏令時(Summer Time ) • 將標準時撥快一小時,分秒不變,恢復時再撥慢一小時。 • 首先提倡者1908年英國議會 夏天時天亮的比較早 理由為:若將時刻改早,則學校工廠機關同樣提早上課上班,而一般人民勢必早睡早起,早睡可節省燈火. 早起可呼吸新鮮空氣,而且接觸日光機會較多,足以增進一般國民的健康。 當時英國學者多數反對,因而英國國會未通過此議案 西元1916年歐戰期問,德國因經濟上關係,首先實行「經濟時」,也就是所謂「日光節約時」,而奧地利、荷蘭、丹麥相繼採用。英國自感驚異,不久亦採用。 我國民國六十九年因民間對於日光節約時反應不佳,停止使用 參考資料: http://e-service.cwb.gov.tw/docs/V3.0/astronomy/calendar/summert.htm
Data Alignment 觀念 • 當你要存取的資料所在位址與資料大小相除餘數 != 0,就會出現這個問題! • char 型態絕不會出現 data alignment 問題 • 考慮下面 DWORD情況 x0007 x0006 x0005 DWORD 以4個 byte 為單位 x0007 x0004 資料發生 Alignment x0006 x0003 x0005 x0002 x0004 x0001 x0003 Buffer x0000 x0002 會產生 alignment 的程式片段 x0001 DWORD data; data=*(DWORD*)((char*) Buffer+1) x0000
Data Alignment 觀念 • 如 32-bit CPU一次讀資料從位址 0 ~ 3到暫存器中, 下次讀資料從位址 4~7 • 絕不會一次由位址2~5 把資料放到暫存器中, 因為 byte 2~5 分別在兩個 word 中 • 如果要讀位址 2~5的資料,必須 • 讀取 0 ~ 3 與 3~7 • 並且做 shift 與 extract 的動作 讀取位址 0 ~3 的資料 取出位址 2 ~3 的資料 讀取位址 4 ~7 的資料 組合 2~3 與 4~5 2 ~ 5 資料
Data Alignment 所造成的問題 • CPU必須進行數次記憶體存取 • 程式效能降低 • 移植程式時會遇到問題 • X86 會自動修正這個問題, • Alpha CPU會丟出 INT 17h 中斷通知 OS EXCEPTION_ DATATYPE_MISALIGNMENT 例外 DWORD data; data=*(DWORD*)((char*) Buffer+1) 利用 Compiler 修正 Data Alignment的問題 DWORD data; data=*(UNALIGNED DWORD*)((char*) Buffer+1)
討論資源的問題 • 當我們可存取的資源有限時,有甚麼好方法進行個 thread 間的管理呢? 我們可以使用 Semaphore管理資源 目前可用的資源數目 設定最大資源數目 HANDLE CreateSemaphore( PSECURITY_ATTRIBUTE psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName);
程式設計的流程 目前可用的資源數目 設定目前資源總數,可用資源 // 目前有2個名額,總共有2個資源 hsem = CreateSemaphore(NULL, 2, 2, NULL); 接受 Request 設定最大資源數目 DWORD dwWaitResult; dwWaitResult=WaitForSingleObject(hsem,0); 判斷資源是否足夠 通知使用者 建立新的 thread 處理 request 釋放資源 ReleaseSemaphore(hsem,1,NULL ) 不需要傳回 previous count
視窗架構 等待時間 = 0 ReleaseSemaphore(hsem,1,NULL ) WaitForSingleObject(hsem,0) 釋放一筆資源 傳回上一次的 可用資源數目 資源不足 SemaphoreDemo
Introduction to Mutex • 保證只有一條 thread可以獨佔資源 • 具有擁有性 (thread ownership ) • Mutex 物件會紀錄擁有者的 thread ID • 當正在等待的 mutex 已經擁有了,則會直接遞增 recursion counter,並繼續執行 • V.S.CriticalSection object • Mutex 比較慢 (因為 mutex 是 kernel object) • 內容: Mutex 的資料內容 Usage counter Thread ID Recursion counter Kernel Object 被索引的次數 ? 擁有者的 thread id 擁有者擁有目前物件的次數
最簡單的 Mutex 範例 Writer 123 123 123 連續寫入資料 連續讀取資料 Reader SimpleMutex
Mutex VS EventMutex ‘s thread ownership InitialOwner: TRUE calling thread 擁有這個mutex Security Attribute Name HANDLE hMutex=CreateMutex(NULL, FALSE, NULL); WaitForSingleObject(hMutex, INFINITE); // 再次進入 WaitForSingleObject(hMutex,INFINITE); cout << "Mutex 使用次數遞增" << endl; RelaseMutex(hMutex); ReleaseMutex(hMutex); 1 對於已經擁有的 Mutex 可以立即的進入 schedulable 階段 2 使用時機: 1. 所以當你的程式要求資料寫完後,必須立即 讀取,則請使用 Event. 2. 當你希望, Reader 與 Writer 能夠獨立楚 理, 則你可以使用 Mutex 不能再繼續寫資料了,會被 Event 擋住 誰先搶到誰就可以繼續進入 請看 Mutex_VS_Event
重要的是: mutex 會記得是誰搶到權杖. 對於 搶到的人有特別的待遇. Event 不管是誰, 只要有人搶到權杖, 別人就要等 補充: 我把 Mutex VS. Event 程式碼列出來 我們現在是討論: 是否可以再進入 Critical Section 的問題 void main(){ HANDLE hMutex=CreateMutex(NULL, FALSE, NULL); WaitForSingleObject(hMutex, INFINITE); // 再次進入 WaitForSingleObject(hMutex,INFINITE); cout << "Mutex 使用次數遞增" << endl; RelaseMutex(hMutex); ReleaseMutex(hMutex); } /* // ====== 比較一下 Event ======== HANDLE hEvent=CreateEvent(NULL,FALSE,TRUE,NULL); WaitForSingleObject(hMutex,INFINITE); // 無法進入 WaitForSingleObject(hEvent,INFINITE); cout << "無法進入" << endl; // ==== End =========== */
放棄 Abandonment Issues (ExitThread, TerminateThread, ExitProcess, TerminateProcess ) • 當一個擁有該 mutex 的 thread 停止執行時, 將沒有任何人可以 release mutex. Abandoned mutex OS 隨時監視系統中的物件, 發現 abandoned mutex 1. Reset the mutex 2. 公平的挑選一個正在等待的 thread 3. Wait function 傳回 WAIT_ABANDONED
有人使用 semorphore 有人使用 mutex 有人在 critical section 中