多線程CreateThread函數的用法


 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM