windows core programming multi-process multi-threading, thread synchronization and mutual exclusion



Source: WeChat public account "Programming Learning Base"

content

Process process

In the windows system, a process is a running program, it has its own virtual address space, its own code, data and other system resources, a process also contains one or more threads running in the process

CreateProcess function

BOOL CreateProcess 
( 
	LPCTSTR lpApplicationName, 	//executable file name
	LPTSTR lpCommandLine, 		//Specifies the parameters to be passed to the execute function
	LPSECURITY_ATTRIBUTES lpProcessAttributes,//NULL
	LPSECURITY_ATTRIBUTES lpThreadAttributes,  //NULL
	BOOL bInheritHandles, 		//Specifies whether it can be inherited by new processes
	DWORD dwCreationFlags, 		//Process priority
	LPVOID lpEnvironment, 		//Environment variables used by the new process
	LPCTSTR lpCurrentDirectory, //new process current directory
	LPSTARTUPINFO lpStartupInfo, //The position, size and standard handle of the main window
	LPPROCESS_INFORMATION lpProcessInformation //Returns the flag information of the process
); 

Process STARTUPINFO

typedef struct _STARTUPINFO 
{ 
	DWORD cb;			//Contains the number of bytes in the STARTUPINFO structure, initializes cb to sizeof(STARTUPINFO) 
    PSTR lpReserved;	//reserve. must be initialized to NULL
    PSTR lpDesktop;		//Specify desktop name
    PSTR lpTitle;		//Console application title
    DWORD dwX;			//The x-coordinate (in pixels) used to set the position of the application window relative to the upper-left corner of the screen. 
    DWORD dwY;		    //For GUI processes use CW_USEDEFAULT as the x and y parameters of CreateWindow
    DWORD dwXSize;		//Used to set the width of the application window (in pixels)
    DWORD dwYSize;			 
    DWORD dwXCountChars;//The number of lines in the console window
    DWORD dwYCountChars; 
    DWORD dwFillAttribute;   //Used to set the text and background colors used by the sub-application's console window 
    DWORD dwFlags;           //See description in next paragraph and Table 4-7 
    WORD wShowWindow;        //window style
    WORD cbReserved2;        //reserve. must be initialized to 0 
    PBYTE lpReserved2;       //reserve. must be initialized to NULL
    HANDLE hStdInput;        //A handle to set the buffer for console input and output.
    HANDLE hStdOutput; 
    HANDLE hStdError; 
} STARTUPINFO, *LPSTARTUPINFO;

Get the STARTUPINFO structure of the current process: GetStartupInfo();

Simple to use

#include<Windows.h>
#include<stdio.h>
int main()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	CreateProcess("C:\\Program Files\\Notepad++\\notepad++.exe",//Who is the process that needs to be created (path file name)
		nullptr,//command line arguments
		nullptr,//Is it inherited by child processes?
		nullptr,//Is it inherited by child threads
		false,//Whether the newly created process inherits the handle from the calling thread
		0,//Create a logo
		nullptr,//Environment block for the new process
		nullptr,//The working path of the child process
		&si,
		&pi);
	printf("new process Id:%d\n", pi.dwProcessId);
	return 0;
}

Running the program will open the NotePad program under the path of C:\Program Files\Notepad++\notepad++.exe on the C drive of the computer

#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;
}

The program runs to create a process and open Notepad, which is opened by parameters

method to end the process

  1. The entry function of the main thread of the process returns (equivalent to ending the created process)
  2. Terminate a child process by calling TerminateProcess on a thread in the parent process
  3. Call ExitProcess somewhere in the current process to end itself
#include<Windows.h>
#include<stdio.h>
int main()
{
	STARTUPINFO si = { sizeof(si) };
	PROCESS_INFORMATION pi;
	CreateProcess("C:\\Program Files\\Notepad++\\notepad++.exe",//Who is the process that needs to be created (path file name)
		nullptr,//command line arguments
		nullptr,//Is it inherited by child processes?
		nullptr,//Is it inherited by child threads
		false,//Whether the newly created process inherits the handle from the calling thread
		0,//Create a logo
		nullptr,//Environment block for the new process
		nullptr,//The working path of the child process
		&si,
		&pi);
	printf("new process Id:%d\n", pi.dwProcessId);
	HANDLE hinst = pi.hProcess;	//Get an example handle for the process
	Sleep(3000);
	//kill the current process
	//The first method is to manually turn off notepad
	TerminateProcess(hinst, NULL);	
	//ExitProcess(0);
	CloseHandle(hinst);
	return 0;
}

Thread thread

CreateThread function

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
	);

Parameter Description:

  • The first parameter lpThreadAttributes represents the security attributes of the thread kernel object. Generally, passing in NULL means using the default settings.
  • The second parameter dwStackSize represents the thread stack space size. Pass in 0 to use the default size (1MB).
  • The third parameter lpStartAddress represents the thread function address executed by the new thread, and multiple threads can use the same function address.
  • The fourth parameter lpParameter is the parameter passed to the thread function.
  • The fifth parameter dwCreationFlags specifies additional flags to control the creation of threads. If it is 0, it means that the thread can be scheduled immediately after it is created. If it is CREATE_SUSPENDED, it means that the thread is suspended after it is created, so that it cannot be scheduled until ResumeThread() is called.
  • The sixth parameter lpThreadId will return the ID number of the thread. Passing in NULL means that the thread ID number does not need to be returned.

return value

If the thread is created successfully, it returns the handle of the new thread. If it fails, it returns NULL.

WaitForSingleObject function

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

The first parameter _In_ HANDLE hHandle is the handle of the object, which can be one of the following:

The second parameter _In_ DWORD dwMilliseconds is the wait time in milliseconds.

The parameter dwMilliseconds has two values ​​with special meaning: 0 and INFINITE.

If it is 0, the function returns immediately;

If it is INFINITE, the thread is suspended until the object pointed to by hHandle becomes signaled.

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

// thread function
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;

	// create a thread
	hThread = CreateThread(
		NULL,		// Default security properties
		NULL,		// Default stack size
		ThreadProc,	// Thread entry address (function of executing thread)
		NULL,		// Parameters passed to the function
		0,		// The specified thread runs immediately
		&dwThreadId);	// Returns the ID number of the thread
	printf(" Now another thread has been created. ID = %d \n", dwThreadId);

	// Wait for the new thread to finish running
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
	return 0;
}

thread synchronization

Each thread can access the public variables and resources in the process, so the problem that needs to be paid attention to in the process of using multithreading is how to prevent two or more threads from accessing the same data at the same time, so as not to destroy the integrity of the data. Mutual constraints between data include
1. Direct constraint relationship, that is, the processing result of one thread is the input of another thread, so threads are directly constrained, this relationship can be called a synchronization relationship
2. Indirect restriction relationship, that is, two threads need to access the same resource, and the resource can only be accessed by one thread at the same time. This relationship is called mutually exclusive access to resources between threads. In a sense, mutual exclusion is A Synchronization with Less Constraints

There are four synchronization methods between windows threads: critical section, mutex, semaphore, and event.

What happens when threads are not synchronized?

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

static int count = 20;
const unsigned int MAX = 4;
// thread function
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;

	// create a thread
	hThread[0] = CreateThread(
		NULL,		// Default security properties
		NULL,		// Default stack size
		ThreadProc,	// Thread entry address (function of executing thread)
		NULL,		// Parameters passed to the function
		0,		// The specified thread runs immediately
		&dwThreadId);	// Returns the ID number of the thread
	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);

	// Wait for the new thread to finish running
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//Wait until all child threads return
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	return 0;
}

operation result:

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

The critical section object is a CRITICAL_SECTION structure defined in the data segment, and some information is recorded through this structure, so that only one thread can access the data in the data segment at the same time.

CRITICAL_SECTION cannot "lock" resources, the function it can complete is to synchronize code segments of different threads.

Initialize the critical section object

void InitializeCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

enter and leave critical section

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

delete critical section object

void DeleteCriticalSection(
  LPCRITICAL_SECTION lpCriticalSection
);

Example of use

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

static int count = 20;
const unsigned int MAX = 4;
CRITICAL_SECTION g_cs;		//Use critical section objects for code sections with synchronization problems
// thread function
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;

	// Initialize the critical section object
	InitializeCriticalSection(&g_cs);

	// create a thread
	hThread[0] = CreateThread(
		NULL,		// Default security properties
		NULL,		// Default stack size
		ThreadProc,	// Thread entry address (function of executing thread)
		NULL,		// Parameters passed to the function
		0,		// The specified thread runs immediately
		&dwThreadId);	// Returns the ID number of the thread
	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);

	// Wait for the new thread to finish running
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//Wait until all child threads return
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	// delete critical section object
	DeleteCriticalSection(&g_cs);
	return 0;
}

output

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

Mutex function

LONG InterlockedDecrement(
  LONG volatile *Addend		//pointer to decremented variable
);
LONG InterlockedIncrement(
  LONG volatile *Addend		//pointer to the incremented variable
);
#include <stdio.h>
#include <windows.h>

static int count = 20;
const unsigned int MAX = 4;
// thread function
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;

	// create a thread
	hThread[0] = CreateThread(
		NULL,		// Default security properties
		NULL,		// Default stack size
		ThreadProc,	// Thread entry address (function of executing thread)
		NULL,		// Parameters passed to the function
		0,		// The specified thread runs immediately
		&dwThreadId);	// Returns the ID number of the thread
	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);

	// Wait for the new thread to finish running
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//Wait until all child threads return
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	return 0;
}

event kernel object

The event kernel object is an abstract object. It has two states: trusted and untrusted. Thread synchronization is achieved by waiting for WaitForSingleObject.

HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,	//security properties
  BOOL                  bManualReset,		//Whether to manually reset the event object to an untrusted object
  BOOL                  bInitialState,		//Specifies the initial state of the event object when it is created
  LPCSTR                lpName				//the name of the event object
);

set kernel object state

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

Wait for the event kernel object to be trusted

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

Example:

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

static int count = 20;
const unsigned int MAX = 4;
HANDLE g_hEvent;
// thread function
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	while (count >= 0)
	{
		/*Waiting for the kernel object to be trusted, the function call ends and the kernel object is set to untrusted*/
		WaitForSingleObject(g_hEvent, INFINITE);
		printf("coutn:%d \tThread:%d\n", count--, GetCurrentThreadId());
		SetEvent(g_hEvent);				/*Set up trusted*/
		Sleep(10);
	}
	return 0;
}

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

	// Create an auto-reset events, trusted (signaled) event kernel object
	g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);

	// create a thread
	hThread[0] = CreateThread(
		NULL,		// Default security properties
		NULL,		// Default stack size
		ThreadProc,	// Thread entry address (function of executing thread)
		NULL,		// Parameters passed to the function
		0,		// The specified thread runs immediately
		&dwThreadId);	// Returns the ID number of the thread
	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);

	// Wait for the new thread to finish running
	WaitForMultipleObjects(MAX, hThread, true, INFINITE);	//Wait until all child threads return
	for (int i = 0; i < MAX; i++)
	{
		CloseHandle(hThread[i]);
	}
	return 0;
}

output

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

To be continued...

Posted by viperfunk on Wed, 04 May 2022 21:10:46 +0300