WindowsAPI 查阅笔记:线程、多个线程互同步

1. 线程的创建

HANDLE CreateThread(
	LPSECURITY_ATTRIBUTES 	lpThreadAttributes, //线程安全属性 
	SIZE_T					dwStackSize,		//线程堆栈大小 
	LPTHREAD_START_ROUTINE 	lpStartAddress,	//重要:  线程函数指针 
	LPVOID 					lpParameter,	//重要:  启动线程函数 
	DWORD 					dwCreationFlags,	//线程安全属性
	LPDWORD 				lpThreadId			//返回 TID 
);//返回新建线程的句柄 

1.1 线程函数

要符合接口参数要求

//ThreadProc 函数原型
DWORD WINAPI ThreadProc(
	LPVOID lpParameter
);

1.2 线程句柄与 TID

每一个线程都有一个句柄和一个标识符 (TID)。
TID 是 DWORD 类型,每个线程的 TID 都不同,所以可以用 TID 标识唯一的 线程。

  • 通过 TID 和 OpenThread() 可以获取线程的句柄。
  • 通过 句柄 和 GetThreadId()可以获取线程的TID。
  • GetCurrentThread() 获取本线程句柄。
  • GetCurrentThreadId() 获取本线程 TID。

1.3 创建线程代码

#include <windows.h>
#include <cstdio>

#define MAX_THREADS 5

typedef struct _THREAD_PARAM{
	DWORD i;
	DWORD dwRandom;
	DWORD dwData;
} THREAD_PARAM, *LPTHREAD_PARAM;

/****************
功能:线程函数,将参数打印出来 
****************/ 
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	//参数数据类型 
	LPTHREAD_PARAM pData;
	pData = (LPTHREAD_PARAM)lpParam;
	
	printf("TID = %u,\tparameters = %u, %u, %u\n",
		GetCurrentThreadId(),
		pData->i,
		pData->dwRandom,
		pData->dwData);
	//释放保存参数的内存(主线程中分配的)
	HeapFree(GetProcessHeap(), 0, pData); 
	return 0;
}

/*********************
功能:创建多个线程 
******************/
int main(int argc, char **argv)
{
	LPTHREAD_PARAM pData;
	DWORD dwThreadId[MAX_THREADS];
	HANDLE hThread[MAX_THREADS];
	int i;
	//创建 MAX_THREADS 个线程 
	for(i = 0 ;i < MAX_THREADS; ++i){
		//为线程函数参数分配内存
		pData = (LPTHREAD_PARAM)HeapAlloc(GetProcessHeap(),
			HEAP_ZERO_MEMORY, sizeof(THREAD_PARAM));
			
		if(pData == NULL){
			printf("HeapAlloc error;\n");
			ExitProcess(2);
		} 
		//设置参数
		pData->i = i;
		pData->dwRandom = rand();
		pData->dwData = 100;
		//创建线程
		hThread[i] = CreateThread(
			NULL,		//默认安全属性
			0,			//默认堆栈大小
			ThreadProc,	//线程函数
			pData,		//参数
			0,			//默认创建标志
			&dwThreadId[i]);//返回TID 
			
		if(hThread[i] == NULL){
			printf("hThread[%d]创建失败!\n",i);
			ExitProcess(i);
		}
		Sleep(10);
	}
	
	//主线程等待其他子线程执行结束
	WaitForMultipleObjects(MAX_THREADS, hThread, TRUE, INFINITE);
	//关闭所有线程的句柄
	for(i = 0; i < MAX_THREADS; ++i){
		CloseHandle(hThread[i]);
	} 
	return 0;
}



代码运行结果:
在这里插入图片描述

2. 线程的挂起、恢复、切换、终止

//schedule.cpp

#include <windows.h>
#include <cstdio>

DWORD WINAPI ThreadProc(LPVOID lpParam)
{
	LPDWORD pData = (LPDWORD)lpParam;
	DWORD i = 0;
	
	for(i = 0; i < 10; ++i){
		Sleep(100);//每 ms 打印一次
		printf("TID = %u,\tparameters = %u\ti = %u\n",
			GetCurrentThreadId(), *pData, i); 
	}
	ExitThread(i);
	return 0;
}
/*********************
功能:线程调度 
*********************/
int main(int argc, char **argv)
{
	DWORD dwData;
	DWORD dwThreadId[2];
	HANDLE hThread[2];
	
	//创建线程
	dwData = 1;
	hThread[0] = CreateThread(
		NULL,0,
		ThreadProc,
		&dwData,
		CREATE_SUSPENDED,	//挂起新建的进程
		&dwThreadId[0] 
	);
	if(hThread[0] == NULL){
		ExitProcess(0); 
	}
	
	//创建线程
	dwData = 2;
	hThread[1] = CreateThread(NULL, 
		0, 
		ThreadProc, 
		&dwData, 
		0,
		&dwThreadId[1]);
	if(hThread[1] == NULL){
		ExitProcess(1);
	} 
	
	//等待 200ms 恢复线程的执行
	Sleep(200);
	ResumeThread(hThread[0]);
	//挂起线程的执行
	SuspendThread(hThread[1]);
	//等待 300 ms 终止线程,恢复线程
	Sleep(300);
	TerminateThread(hThread[0], 0);
	ResumeThread(hThread[1]);
	
	//等待所有线程执行结束
	WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
	//关闭所有线程的句柄
	CloseHandle(hThread[0]);
	CloseHandle(hThread[1]);
	
	return 0;
}

运行结果:
在这里插入图片描述

3. 等待函数

等待函数可以等待同步对象以外,还可以等待其他对象,包括进程和线程。以进程或线程句柄为等待对象就可以了。
以下两个是下面的同步代码中用到的。
在下面代码中用到的时候添加了注释。

3.1 等待函数

//WaitForSingleObject 
//功能是等待单个对象,如果对象被标记,则返回
DWORD WINAPI WaitForSingleObject(
	DANDLE hHandle,
	DWORD dwMilliseconds
);

//WaitForMultipleObjects 
// 功能是等待多个对象,等待的所有对象都设置为标记的 / 有一个设置为标记的 则返回.
DWORD WINAPI WaitForMultipleObjects(
	DWORD nCount,
	const HANDLE *lpHandles,
	BOOL bWaitAll,
	DWORD dwMilliseconds
);

3.2 标记函数

设置标记:

//设置标记
//将事件对象设置为标记
BOOL WINAPI SetEvent(HANDLE hEvent);

重置标记:

//重置标记
//如果事件设置为手工重置,那么需要使用此函数来重置事件
BOOL WINAPI ResetEvent(HANDLE hEvent);

获取事件句柄:

//从事件名中获取事件句柄
HANDLE WINAPI OpenEvent(
	DWORD dwDesiredAccess,
	BOOL bInheritHandle,
	LPCTSTR lpName
);

4. 多个线程互同步

创建三个线程,对一个全局变量进行读操作。
通过三个读事件和写事件控制三个线程,避免在写入数据的时候发生【读事件】。

  1. 主线程创建3个线程,创建【读事件】和【写事件】,并设置为false.
  2. 主线程开始写入数据,之后打开【写事件】,并开始等待【读事件】。
  3. 子线程之前的【写事件】状态是关闭,所以在等待。【写事件】打开后,开始执行打印数据操作。然后打开【读事件】。
  4. 最后关闭子进程。
#include <windows.h>
#include <cstdio>

//常量定义 
#define NUMTHREADS 3
#define BUFFER_SIZE 16
#define FOR_TIMES 5

//全局变量
HANDLE hThread[NUMTHREADS]; 
//写 event 表示写操作是否完成 
HANDLE hWriteEvent[NUMTHREADS];
//读 event 表示读操作是否完成 
HANDLE hReadEvents[NUMTHREADS]; 
//共享内存 
// BYTE lpSharedBuffer[16] = {0};
LPSTR lpSharedBuffer;
DWORD* index;

//函数声明
void MultiEvents(void);
void WriteToBuffer(void);
DWORD WINAPI ThreadFunction(LPVOID lpParam);
/******************
int main(void)
******************/ 
int main(void)
{
	index = (DWORD*)malloc(sizeof(DWORD));
	lpSharedBuffer = (char*)malloc(sizeof(char)*64);
	
	MultiEvents();
	
	free(index);
	free(lpSharedBuffer);
	return 0;
}
/*******************
演示 event 的使用方法 
******************/
void MultiEvents(void)
{
//	HANDLE hThread;
	DWORD i = 0;
	
	*index = i;

	//创建多个线程,读共享内存,主线程写共享内存
	//每个线程都有对应的读写同步事件
	for(i = 0; i < NUMTHREADS; ++i){
		*index = i;
		//每个线程都有一个 event 表示写入操作完成
		hWriteEvent[i] = CreateEvent(
			NULL,		//默认安全属性
			FALSE,		//自动重置
			FALSE,		//初始化为未置位的
			NULL		//未命名 
		);
		//判断是否创建成功
		if(hWriteEvent[i] == NULL){
			printf("CreateEvent failed (%d)\n",GetLastError());
			return ;
		} 
		
		//每个线程都有一个 event 表示读入操作完成
		hReadEvents[i] = CreateEvent(
			NULL,		//默认安全属性
			FALSE,		//自动重置
			FALSE,		//初始化为未置位的
			NULL		//未命名 
		);
		//判断是否创建成功
		if(hReadEvents[i] == NULL){
			printf("CreateEvent failed (%d)\n",GetLastError());
			return ;
		} 
		
		
		//创建线程

		hThread[i] = CreateThread(NULL, 0, 
			ThreadFunction, 
			index, 
			0, NULL
		);
			
		if(hThread[i] == NULL){
			printf("CreateThread failed (%d)\n",GetLastError());
			return ;
		} 
		printf("CreateThread: %d\n",i);

	} 
	WriteToBuffer();
	
	
	//主线程等待其他子线程执行结束
	WaitForMultipleObjects(NUMTHREADS, 
		hThread, TRUE, INFINITE);
	//关闭所有线程的句柄
	for(i = 0; i < NUMTHREADS; ++i){
		CloseHandle(hThread[i]);
		printf("CloseThread:%d\n",i);
	} 
	

	return ;
}

/************************
由主线程调用,向共享内存中写入数据,
等待所有读线程读完后函数返回 
*********************/
void WriteToBuffer(void)
{
	DWORD dwWaitResult, j, i;
	//完成 for_times 次读写
	for(j = 0;j < FOR_TIMES; ++j){
		//写入需要的时间间隔 
		Sleep(rand()%100);
		//写入共享内存
		wsprintf(lpSharedBuffer,"shared %d",j);
		//将线程对应的写 Event 置为 "标志的",表示写操作完成
		//其他线程可以开始读
		for(i = 0; i < NUMTHREADS; ++i){
			if(! SetEvent(hWriteEvent[i])){
				printf("SetEvent failed (%d)\n",GetLastError());
				return ;
			}
		} 
		dwWaitResult = WaitForMultipleObjects(
			NUMTHREADS,		//Event 句柄的个数
			hReadEvents,	//Event句柄数组
			TRUE,			//等到所有的event都被标志
			INFINITE 		//无线等待 
		);
		//判断等待结果
		if(dwWaitResult != WAIT_OBJECT_0){
			printf("Wait errpr (%d)\n",GetLastError());
			ExitProcess(0);
		} 
	} 
	return ;
} 

/*******************
线程函数,读共享内存 
*******************/
DWORD WINAPI ThreadFunction(LPVOID lpParam)
{
	DWORD dwWaitResult;
	LPSTR lpRead[16];
	DWORD j = 0;
	DWORD dwThreadIndex = *(DWORD*)lpParam;
	printf("Hello! dwThreadIndex = %d\n",dwThreadIndex);
	//完成 FOR_TIMES 次读写
	for(; j < FOR_TIMES; ++j)
	{
		//等待写事件置位,表示数据已经写入
		dwWaitResult =  WaitForSingleObject(
			hWriteEvent[dwThreadIndex],	//event 句柄 
			INFINITE					//无限等待 
		);
		
		switch(dwWaitResult)
		{
			case WAIT_OBJECT_0:
					//模拟数据处理需要的时间间隔 
				Sleep(rand()%10);
				CopyMemory(lpRead, lpSharedBuffer, 16);
				break;
				//发生错误
			default:
				printf("wait errpr: %d\n",GetLastError());
				ExitProcess(0);
		}
		//将读 event 置位,表示读操作完成
		if(!SetEvent(hReadEvents[dwThreadIndex])){
			printf("SetEvent failed (%d)\n",GetLastError());
			return 0;
		} 
		//打印读到的内容
		printf("线程 %u\t第 %d 次读, 内容: %s\n",
			dwThreadIndex,j,(LPSTR)lpRead);
	} 
	return 1;
}

运行结果:
在这里插入图片描述

5. C++11 中的互斥

#include <iostream>
#include <thread>
#include <mutex>
#include <windows.h>

std::mutex mtx;	//全局互斥锁
int shared_resource = 0;

void Num1(void){
	for(int i = 0; i < 3; ++i){
		mtx.lock();//锁定互斥锁
		++shared_resource;
		printf("This is AddNum!\n");
		mtx.unlock();//解锁互斥锁 
	}
	return ;
}
void Num2(void){
	for(int i = 0; i < 3; ++i){
		mtx.lock();
		++shared_resource;
		printf("This is PrintNum!\n");
		mtx.unlock();
	}
	return ;
}

int main(void){
	
	std::thread t1(Num1);
	std::thread t2(Num2);
	
	t1.join();
	t2.join();
	
	std::cout <<"Final value of shared_resource: " << shared_resource << std::endl;
	
	return 0;
}

运行结果:
在这里插入图片描述

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值