1. 程式人生 > 實用技巧 >windows核心程式設計之多程序多執行緒,執行緒的同步互斥

windows核心程式設計之多程序多執行緒,執行緒的同步互斥



來源:微信公眾號「程式設計學習基地」

目錄

Process程序

在windows系統中,程序就是一個正在執行的程式,他擁有自己的虛擬地址空間,擁有自己的程式碼,資料和其他系統資源,一個程序也包含了一個或多個執行在此程序中的執行緒

CreateProcess函式

BOOL CreateProcess 
( 
	LPCTSTR lpApplicationName, 	//可執行檔名稱
	LPTSTR lpCommandLine, 		//指定了要傳遞給執行函式的引數
	LPSECURITY_ATTRIBUTES lpProcessAttributes,//NULL
	LPSECURITY_ATTRIBUTES lpThreadAttributes,  //NULL
	BOOL bInheritHandles, 		//指定是否可以被新程序繼承
	DWORD dwCreationFlags, 		//程序優先順序
	LPVOID lpEnvironment, 		//新程序使用的環境變數
	LPCTSTR lpCurrentDirectory, //新程序當前目錄
	LPSTARTUPINFO lpStartupInfo, //主視窗的位置,大小和標準控制代碼
	LPPROCESS_INFORMATION lpProcessInformation //返回程序的標誌資訊
); 

程序STARTUPINFO

typedef struct _STARTUPINFO 
{ 
	DWORD cb;			//包含STARTUPINFO結構中的位元組數,將cb初始化為sizeof(STARTUPINFO) 
    PSTR lpReserved;	//保留。必須初始化為NULL
    PSTR lpDesktop;		//指定桌面名稱
    PSTR lpTitle;		//控制檯應用程式標題
    DWORD dwX;			//用於設定應用程式視窗相對螢幕左上角位置的x 座標(以畫素為單位)。 
    DWORD dwY;		    //對於GUI processes用CW_USEDEFAULT作為CreateWindow的x、y引數
    DWORD dwXSize;		//用於設定應用程式視窗的寬度(以畫素為單位)
    DWORD dwYSize;			 
    DWORD dwXCountChars;//控制檯視窗的行數
    DWORD dwYCountChars; 
    DWORD dwFillAttribute;   //用於設定子應用程式的控制檯視窗使用的文字和背景顏色 
    DWORD dwFlags;           //請參見下一段和表4-7 的說明 
    WORD wShowWindow;        //視窗的風格
    WORD cbReserved2;        //保留。必須被初始化為0 
    PBYTE lpReserved2;       //保留。必須被初始化為NULL
    HANDLE hStdInput;        //用於設定供控制檯輸入和輸出用的快取的控制代碼。
    HANDLE hStdOutput; 
    HANDLE hStdError; 
} STARTUPINFO, *LPSTARTUPINFO;

獲取當前程序的STARTUPINFO結構:GetStartupInfo();

簡單使用

#include<Windows.h>
#include<stdio.h>
int main()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	CreateProcess("C:\\Program Files\\Notepad++\\notepad++.exe",//需要建立的程序是誰(路徑檔名)
		nullptr,//命令列引數
		nullptr,//是否被子程序所繼承
		nullptr,//是否被子執行緒所繼承
		false,//新建立的程序是否從呼叫執行緒處繼承了控制代碼
		0,//建立標誌
		nullptr,//新程序的環境塊
		nullptr,//子程序的工作路徑
		&si,
		&pi);
	printf("新的程序Id:%d\n", pi.dwProcessId);
	return 0;
}

程式執行會開啟電腦C盤C:\\Program Files\\Notepad++\\notepad++.exe路徑下的NotePad程式

#include<Windows.h>
int main()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	char* Command = "notepad";
	CreateProcess(NULL, Command, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);
	return 0;
}

程式執行建立程序,開啟記事本,這是通過引數開啟的

結束程序的方法

  1. 該程序的主執行緒的入口函式返回(等同於你結束建立的程序)
  2. 在父程序的某個執行緒處呼叫TerminateProcess去結束一個子程序
  3. 在當前程序的某個位置呼叫ExitProcess去結束自己
#include<Windows.h>
#include<stdio.h>
int main()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	CreateProcess("C:\\Program Files\\Notepad++\\notepad++.exe",//需要建立的程序是誰(路徑檔名)
		nullptr,//命令列引數
		nullptr,//是否被子程序所繼承
		nullptr,//是否被子執行緒所繼承
		false,//新建立的程序是否從呼叫執行緒處繼承了控制代碼
		0,//建立標誌
		nullptr,//新程序的環境塊
		nullptr,//子程序的工作路徑
		&si,
		&pi);
	printf("新的程序Id:%d\n", pi.dwProcessId);
	HANDLE hinst = pi.hProcess;	//獲取該程序的示例控制代碼
	Sleep(3000);
	//終止當前程序
	//第一種方法就是手動關掉notepad
	TerminateProcess(hinst, NULL);	
	//ExitProcess(0);
	CloseHandle(hinst);
	return 0;
}

Thread執行緒

CreateThread函式

HANDLE WINAPI CreateThread(
	  _In_opt_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,   
	  _In_      SIZE_T                 dwStackSize,
	  _In_      LPTHREAD_START_ROUTINE lpStartAddress,
	  _In_opt_  LPVOID                 lpParameter,
	  _In_      DWORD                  dwCreationFlags,
	  _Out_opt_ LPDWORD                lpThreadId
	);

引數說明:

  • 第一個引數 lpThreadAttributes 表示執行緒核心物件的安全屬性,一般傳入NULL表示使用預設設定。
  • 第二個引數 dwStackSize 表示執行緒棧空間大小。傳入0表示使用預設大小(1MB)。
  • 第三個引數 lpStartAddress 表示新執行緒所執行的執行緒函式地址,多個執行緒可以使用同一個函式地址。
  • 第四個引數 lpParameter 是傳給執行緒函式的引數。
  • 第五個引數 dwCreationFlags 指定額外的標誌來控制執行緒的建立,為0表示執行緒建立之後立即就可以進行排程,如果為CREATE_SUSPENDED則表示執行緒建立後暫停執行,這樣它就無法排程,直到呼叫ResumeThread()。
  • 第六個引數 lpThreadId 將返回執行緒的ID號,傳入NULL表示不需要返回該執行緒ID號。

返回值

執行緒建立成功返回新執行緒的控制代碼,失敗返回NULL

WaitForSingleObject函式

DWORD WINAPI WaitForSingleObject(
    _In_ HANDLE hHandle,
    _In_ DWORD dwMilliseconds
    );

第一個引數 _In_ HANDLE hHandle 是物件的控制代碼,可以是以下幾種:

第二個引數 _In_ DWORD dwMilliseconds 為等待時間,以毫秒為單位。

引數dwMilliseconds有兩個具有特殊意義的值:0和INFINITE。

若為0,則該函式立即返回;

若為INFINITE,則執行緒一直被掛起,直到hHandle所指向的物件變為有訊號狀態時為止。

#include <stdio.h>
#include <windows.h>

// 執行緒函式
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	for (int i = 0; i < 10; i++)
	{
		printf("I am Thread :%d\trun\n", GetCurrentThreadId());
		
	}
	return 0;
}

int  main(int argc, char* argv[])
{
	HANDLE hThread;
	DWORD dwThreadId;

	// 建立一個執行緒
	hThread = CreateThread(
		NULL,		// 預設安全屬性
		NULL,		// 預設堆疊大小
		ThreadProc,	// 執行緒入口地址(執行執行緒的函式)
		NULL,		// 傳給函式的引數
		0,		// 指定執行緒立即執行
		&dwThreadId);	// 返回執行緒的ID號
	printf(" Now another thread has been created. ID = %d \n", dwThreadId);

	// 等待新執行緒執行結束
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
	return 0;
}

執行緒同步

每個執行緒都可以訪問程序中的公共變數,資源,所以使用多執行緒的過程中需要注意的問題是如何防止兩個或兩個以上的執行緒同時訪問同一個資料,以免破壞資料的完整性。資料之間的相互制約包括
1、直接制約關係,即一個執行緒的處理結果,為另一個執行緒的輸入,因此執行緒之間直接制約著,這種關係可以稱之為同步關係
2、間接制約關係,即兩個執行緒需要訪問同一資源,該資源在同一時刻只能被一個執行緒訪問,這種關係稱之為執行緒間對資源的互斥訪問,某種意義上說互斥是一種制約關係更小的同步

windows執行緒間的同步方式有四種:臨界區、互斥量、訊號量、事件。

執行緒不同步會出現什麼情況呢?

#include <stdio.h>
#include <windows.h>

static int count = 20;
const unsigned int MAX = 4;
// 執行緒函式
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	while (count >= 0)
	{
		printf("coutn:%d \tThread:%d\n", count--, GetCurrentThreadId());
	}
	return 0;
}

int  main(int argc, char* argv[])
{
	HANDLE hThread[MAX];
	DWORD dwThreadId;

	// 建立一個執行緒
	hThread[0] = CreateThread(
		NULL,		// 預設安全屬性
		NULL,		// 預設堆疊大小
		ThreadProc,	// 執行緒入口地址(執行執行緒的函式)
		NULL,		// 傳給函式的引數
		0,		// 指定執行緒立即執行
		&dwThreadId);	// 返回執行緒的ID號
	hThread[1] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[2] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[3] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);

	// 等待新執行緒執行結束
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//一直等待,直到所有子執行緒全部返回
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	return 0;
}

執行結果:

coutn:20        Thread:9104
coutn:18        Thread:9104
coutn:17        Thread:9104
coutn:15        Thread:14248
coutn:13        Thread:14248
coutn:16        Thread:10472
coutn:11        Thread:10472
coutn:10        Thread:10472
coutn:9         Thread:10472
coutn:8         Thread:10472
coutn:7         Thread:10472
coutn:6         Thread:10472
coutn:5         Thread:10472
coutn:4         Thread:10472
coutn:3         Thread:10472
coutn:2         Thread:10472
coutn:1         Thread:10472
coutn:0         Thread:10472
coutn:14        Thread:9104
coutn:19        Thread:3076
coutn:12        Thread:14248

臨界區

臨界區物件時定義在資料段中的一個CRITICAL_SECTION結構,通過這個結構記錄一些資訊,以達到在同一時間只有一個執行緒訪問該資料段中的資料。

CRITICAL_SECTION是不能夠“鎖定”資源的,它能夠完成的功能,是同步不同執行緒的程式碼段。

初始化臨界區物件

void InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

進入離開臨界區

void EnterCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);
void LeaveCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

刪除臨界區物件

void DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

使用示例

#include <stdio.h>
#include <windows.h>

static int count = 20;
const unsigned int MAX = 4;
CRITICAL_SECTION g_cs;		//對存在同步問題的程式碼段使用臨界區物件
// 執行緒函式
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	while (count >= 0)
	{
		EnterCriticalSection(&g_cs);
		printf("coutn:%d \tThread:%d\n", count--, GetCurrentThreadId());
		LeaveCriticalSection(&g_cs);
		Sleep(1);
	}
	return 0;
}

int  main(int argc, char* argv[])
{
	HANDLE hThread[MAX];
	DWORD dwThreadId;

	// 初始化臨界區物件
	InitializeCriticalSection(&g_cs);

	// 建立一個執行緒
	hThread[0] = CreateThread(
		NULL,		// 預設安全屬性
		NULL,		// 預設堆疊大小
		ThreadProc,	// 執行緒入口地址(執行執行緒的函式)
		NULL,		// 傳給函式的引數
		0,		// 指定執行緒立即執行
		&dwThreadId);	// 返回執行緒的ID號
	hThread[1] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[2] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[3] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);

	// 等待新執行緒執行結束
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//一直等待,直到所有子執行緒全部返回
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	// 刪除臨界區物件
	DeleteCriticalSection(&g_cs);
	return 0;
}

輸出

coutn:20        Thread:5340
coutn:19        Thread:5808
coutn:18        Thread:3884
coutn:17        Thread:5340
coutn:16        Thread:1772
coutn:15        Thread:1772
coutn:14        Thread:5340
coutn:13        Thread:3884
coutn:12        Thread:5808
coutn:11        Thread:3884
coutn:10        Thread:5340
coutn:9         Thread:1772
coutn:8         Thread:5808
coutn:7         Thread:3884
coutn:6         Thread:5340
coutn:5         Thread:1772
coutn:4         Thread:5808
coutn:3         Thread:1772
coutn:2         Thread:3884
coutn:1         Thread:5808
coutn:0         Thread:5340

互斥函式

LONG InterlockedDecrement(
  LONG volatile *Addend		//指向遞減的變數
);
LONG InterlockedIncrement(
  LONG volatile *Addend		//指向遞增的變數
);
#include <stdio.h>
#include <windows.h>

static int count = 20;
const unsigned int MAX = 4;
// 執行緒函式
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	while(count >= 0)
	{
		InterlockedDecrement((long*)&count);
		printf("coutn:%d \tThread:%d\n", count, GetCurrentThreadId());
		Sleep(10);
	}
	return 0;
}

int main(int argc, char* argv[])
{
	HANDLE hThread[MAX];
	DWORD dwThreadId;

	// 建立一個執行緒
	hThread[0] = CreateThread(
		NULL,		// 預設安全屬性
		NULL,		// 預設堆疊大小
		ThreadProc,	// 執行緒入口地址(執行執行緒的函式)
		NULL,		// 傳給函式的引數
		0,		// 指定執行緒立即執行
		&dwThreadId);	// 返回執行緒的ID號
	hThread[1] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[2] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[3] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);

	// 等待新執行緒執行結束
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//一直等待,直到所有子執行緒全部返回
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	return 0;
}

事件核心物件

事件核心物件是一種抽象的物件,它有受信和未授信兩種狀態,通過等待WaitForSingleObject實現執行緒同步

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,	//安全屬性
  BOOL                  bManualReset,		//是否手動重置事件物件為未受信物件
  BOOL                  bInitialState,		//指定事件物件建立時的初始狀態
  LPCSTR                lpName				//事件物件的名稱
);

設定核心物件狀態

BOOL SetEvent(
  HANDLE hEvent
);
BOOL ResetEvent(
  HANDLE hEvent
);

等待事件核心物件受信

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

示例:

#include <stdio.h>
#include <windows.h>

static int count = 20;
const unsigned int MAX = 4;
HANDLE g_hEvent;
// 執行緒函式
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	while (count >= 0)
	{
		/*等待核心物件受信,函式呼叫結束核心物件被設定為未授信*/
		WaitForSingleObject(g_hEvent, INFINITE);
		printf("coutn:%d \tThread:%d\n", count--, GetCurrentThreadId());
		SetEvent(g_hEvent);				/*設定受信*/
		Sleep(10);
	}
	return 0;
}

int  main(int argc, char* argv[])
{
	HANDLE hThread[MAX];
	DWORD dwThreadId;

	// 建立一個自動重置的(auto-reset events),受信的(signaled)事件核心物件
	g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);

	// 建立一個執行緒
	hThread[0] = CreateThread(
		NULL,		// 預設安全屬性
		NULL,		// 預設堆疊大小
		ThreadProc,	// 執行緒入口地址(執行執行緒的函式)
		NULL,		// 傳給函式的引數
		0,		// 指定執行緒立即執行
		&dwThreadId);	// 返回執行緒的ID號
	hThread[1] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[2] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);
	hThread[3] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, &dwThreadId);

	// 等待新執行緒執行結束
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//一直等待,直到所有子執行緒全部返回
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	return 0;
}

輸出

coutn:20        Thread:3156
coutn:19        Thread:9360
coutn:18        Thread:11444
coutn:17        Thread:17128
coutn:16        Thread:3156
coutn:15        Thread:9360
coutn:14        Thread:11444
coutn:13        Thread:17128
coutn:12        Thread:3156
coutn:11        Thread:17128
coutn:10        Thread:9360
coutn:9         Thread:11444
coutn:8         Thread:3156
coutn:7         Thread:17128
coutn:6         Thread:9360
coutn:5         Thread:11444
coutn:4         Thread:3156
coutn:3         Thread:11444
coutn:2         Thread:9360
coutn:1         Thread:17128
coutn:0         Thread:3156

未完待續...