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

未完待续...

posted @ 2020-12-01 12:33  DeRoy  阅读(546)  评论(0编辑  收藏  举报