CreateThread
當使用CreateProcess調用時,系統將創建一個進程和一個主線程。CreateThread將在主線程的基礎上創建一個新線程,大致做如下步驟:
1在內核對象中分配一個線程標識/句柄,可供管理,由CreateThread返回
2把線程退出碼置為STILL_ACTIVE,把線程掛起計數置1
3分配context結構
4分配兩頁的物理存儲以准備棧,保護頁設置為PAGE_READWRITE,第2頁設為PAGE_GUARD
5lpStartAddr和lpvThread值被放在棧頂,使它們成為傳送給StartOfThread的參數
6把context結構的棧指針指向棧頂(第5步)指令指針指向startOfThread函數
HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, //線程安全相關的屬性,常置為NULL SIZE_T dwStackSize, //新線程的初始化棧在大小,可設置為0 LPTHREAD_START_ROUTINE lpStartAddress, //被線程執行的回調函數,也稱為線程函數 LPVOID lpParameter, //傳入線程函數的參數,不需傳遞參數時為NULL DWORD dwCreationFlags, //控制線程創建的標志 LPDWORD lpThreadId //傳出參數,用於獲得線程ID,如果為NULL則不返回線程ID );
第一個參數是指向SECURITY_ATTRIBUTES型態的結構的指針。在Windows 98中忽略該參數。在Windows NT中,它被設為NULL。
第二個參數是用於新線程的初始堆棧大小,默認值為0。在任何情況下,Windows根據需要動態延長堆棧的大小。
第三個參數是指向線程函數的指標。函數名稱沒有限制,但是必須以下列形式聲明:
DWORD WINAPI ThreadProc (PVOID pParam) ;
第四個參數為傳遞給ThreadProc的參數。這樣主線程和從屬線程就可以共享數據。
第五個參數通常為0,但當建立的線程不馬上執行時為旗標CREATE_SUSPENDED。線程將暫停直到呼叫ResumeThread來恢復線程的執行為止。表示創建線程的運行狀態,其中CREATE_SUSPEND表示掛起當前創建的線程,而0表示立即執行當前創建的進程;
第六個參數 lpThreadID:返回新創建的線程的ID編號;是一個指標,指向接受執行緒ID值的變量。
如果函數調用成功,則返回新線程的句柄,調用WaitForSingleObject函數等待所創建線程的運行結束。函數的格式如下:
參數的含義如下:
hHandle:指定對象或時間的句柄;
dwMilliseconds:等待時間,以毫秒為單位,當超過等待時間時,此函數返回。如果參數設置為0,則該函數立即返回;如果設置成INFINITE,則該函數直到有信號才返回。
一般情況下需要創建多個線程來提高程序的執行效率,但是多個線程同時運行的時候可能調用線程函數,在多個線程同時對一個內存地址進行寫入操作,由於CPU時間調度的問題,寫入的數據會被多次覆蓋,所以要使線程同步。
就是說,當有一個線程對文件進行操作時,其它線程只能等待。可以通過臨界區對象實現線程同步。臨界區對象是定義在數據段中的一個CRITICAL_SECTION結構,Windows內部使用這個結構記錄一些信息,確保同一時間只有一個線程訪問改數據段中的數據。
使用臨界區的步驟如下:
(1)初始化一個CRITICAL_SECTION結構;在使用臨界區對象之前,需要定義全局CRITICAL_SECTION變量,在調用CreateThread函數前調用InitializeCriticalSection函數初始化臨界區對象;
(2)申請進入一個臨界區;在線程函數中要對保護的數據進行操作前,可以通過調用EnterCriticalSection函數申請進入臨界區。由於同一時間內只能有一個線程進入臨界區,所以在申請的時候如果有一個線程已經進入臨界區,則該函數就會一直等到那個線程執行完臨界區代碼;
(3)離開臨界區;當執行完臨界區代碼后,需要調用LeaveCriticalSection函數離開臨界區;
(4)刪除臨界區;當不需要臨界區時調用DeleteCriticalSection函數將臨界區對象刪除;
下面的代碼創建了5個線程,每個線程在文件中寫入10000個“hello”:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
注意:臨界區要在線程執行前初始化,因為線程一但被建立即開始運行(除非手工掛起),但線程建立后在初始化臨界區可能出現問題
#include <stdlib.h> #include <iostream> #include <list> #include <conio.h> #include <time.h> #include <algorithm> #include <windows.h> using namespace std; //volatile是一個類型修飾符,表示這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了 volatile int b = 0; //int b = 0; DWORD WINAPI ThreadProc(LPVOID lpParameter) { int i = 10000; int *p = (int*)lpParameter; while(i--) { (*p)++; b++; } return 0; } int main(int argc, char* argv[]) { int a = 0; HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL); HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL); HANDLE hThread3 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL); HANDLE hThread4 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL); HANDLE hThread5 = CreateThread(NULL, 0, ThreadProc, &a, 0, NULL); Sleep(1000); CloseHandle(hThread1); CloseHandle(hThread2); CloseHandle(hThread3); CloseHandle(hThread4); CloseHandle(hThread5); cout << "a = " << a << endl; cout << "b = " << b << endl; system("pause"); return 0; }
每次運行 得到的結果有可能不一致
a = 43955
b = 42426
//
a = 50000
b = 50000
回調函數必須是靜態成員函數或全局函數 ,不放在類里,不必static,
放在類里一定要static
