win32 相关同步api

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了win32 相关同步api相关的知识,希望对你有一定的参考价值。

前言

在Win32编程中有时候我们希望在进程/线程中进行所谓的互斥操作。比如A线程执行时A方法时,B线程禁止执行A方法。当然我们的同步互斥需求也可能会蔓延到进程级别的。下文的API既可以在多线程中使用也可以在多进程中使用。

句柄的有信号和无信号

下面的对象句柄可以设置成有信号和无信号状态,可以用来配合其他配合完成同步操作。

  1. Change notification
  2. Console input
  3. Event
  4. Memory resource notification
  5. Mutex
  6. Process
  7. Semaphore
  8. Thread
  9. Waitable timer

当然上面的一些对象会自动改变句柄的有信号和无信号状态,比如线程对象Thread.Thread在运行时是无信号状态,在线程退出时自动变为有信号状态。

微软提供了WaitForSingleObject可以让我们监听对象句柄的有信号状态。

WaitForSingleObject 官方文档

我们看下函数的声明:

DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

函数等候对应hHandle的对象变为有信号或者到达dwMilliseconds指向的时间后返回。

  • 参数1 hHandle
    对象的句柄

  • 参数2 dwMilliseconds
    可以设置句柄没有信号时最多阻塞多少毫秒。如果为INFINITE那么就会无休止的等候直到句柄对象变为有信号状态

Event和Thread

#include <iostream>
#include <Windows.h>
#include<tlhelp32.h>
using namespace std;
#include <string> 

HANDLE evenHandle;

int i = 0;
DWORD WINAPI myRun(
	_In_ LPVOID lpParameter
) {


	cout << "子线程运行" << endl;
	WaitForSingleObject(evenHandle, INFINITE);
	cout << "子线程运行 WaitForSingleObject 后一条代码" << endl;
	//重置句柄为无信号状态
	ResetEvent(evenHandle);
	cout << "子线程运行 ResetEvent(evenHandle) 后一条代码" << endl;
	WaitForSingleObject(evenHandle, INFINITE);
	cout << "子线程运行 ResetEvent(evenHandle)和WaitForSingleObject(evenHandle, INFINITE) 后一条代码" << endl;


	//退出代码
	return EXIT_SUCCESS;
}

int main()
{

	//这个API创建一个Event对象然后这个Event的句柄,上文我们看到过Event对象句柄具有有信号和无信号状态
	//https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa
	evenHandle = CreateEvent(NULL,
		TRUE,//设置是否手动更改信号状态。这个参数不好描述,简而言之就是WaitForSingleObject获取信号后需要手动调用ResetEvent 函数将状态变为无信号。如果为FALSE,在WaitForSingleObject后自动变为无信号
		FALSE,//设置Event对象一开始是有信号状态还是无信号状态
		NULL//多进程可以用到
		);


	//线程id
	DWORD threadId;

	HANDLE threadHandle = CreateThread(NULL,//是否返回的句柄能让子进程继承
		0,//设置线程栈大小。越小递归深度越深,但是栈帧内存越小,0是默认设置
		myRun,//方法指针,线程运行时会调用这个函数
		NULL,//可传入任意的指针给线程参数
		CREATE_SUSPENDED,//线程启动参数,传入0线程创建时立即运行。CREATE_SUSPENDED会在ResumeThread 才运行
		&threadId//传出 线程id
	);
	//启动线程
	ResumeThread(threadHandle);



	//让子线程运行
	Sleep(100);

	cout << "主线程设置句柄为有信号" << endl;
	SetEvent(evenHandle);
	//让子线程运行
	Sleep(100);
	//子线程内部让句柄变为无信号然后继续阻塞
	//再次唤醒子线程
	cout << "222 主线程设置句柄为有信号" << endl;
	SetEvent(evenHandle);

	//等候线程结束,Thread句柄也具有无信号和有信号状态哦,启动时为无信号退出线程为有信号
	WaitForSingleObject(threadHandle, INFINITE);

	cout << "主线程结束 " << i << endl;

	CloseHandle(threadHandle);

	return EXIT_SUCCESS;

}

在这里插入图片描述

上面的例子我们可以观察到两个对象的有信号和无信号状态,一个是Thread,一个Event.

Mutext

我们再看一个Mutext使用案例

#include <iostream>
#include <Windows.h>
#include<tlhelp32.h>
using namespace std;
#include <string> 

HANDLE ghMutex;

int i = 0;
DWORD WINAPI myRun(
	_In_ LPVOID lpParameter
) {


	cout << "子线程运行" << endl;
	DWORD  dwWaitResult = WaitForSingleObject(
		ghMutex,    // handle to mutex
		INFINITE);  // no time-out interval



	cout << "子线程运行 在一个死循环中" << endl;

	Sleep(1000);


	//退出代码
	return EXIT_SUCCESS;
}

int main()
{
	ghMutex = CreateMutex(
		NULL,              // 安全属性这里视为空
		TRUE,             // 当前互斥量 是否被创建时的线程持有这个锁,还是得到到第一个WaitForSingleObject调用线程
		NULL);             // 可以用指定互斥量名称

	if (ghMutex == NULL)
	{
		printf("CreateMutex error: %d\\n", GetLastError());
		ReleaseMutex(ghMutex);
		return 1;
	}


	DWORD threadId;

	HANDLE threadHandle = CreateThread(NULL,//是否返回的句柄能让子进程继承
		0,//设置线程栈大小。越小递归深度越深,但是栈帧内存越小,0是默认设置
		myRun,//方法指针,线程运行时会调用这个函数
		NULL,//可传入任意的指针给线程参数
		CREATE_SUSPENDED,//线程启动参数,传入0线程创建时立即运行。CREATE_SUSPENDED会在ResumeThread 才运行
		&threadId//传出 线程id
	);

	ResumeThread(threadHandle);



	//让子线程运行
	Sleep(100);
	cout << "主线程 设置ghMutex有信号" << endl;
	//让ghMutex变为有信号
	ReleaseMutex(ghMutex);

	

	
	等候线程结束
	WaitForSingleObject(threadHandle, INFINITE);

	cout << "主线程结束 " << i << endl;


	CloseHandle(threadHandle);
	CloseHandle(ghMutex);


	return EXIT_SUCCESS;

}

在这里插入图片描述
我们修改下互斥量构造函数

	ghMutex = CreateMutex(
		NULL,              // 安全属性这里视为空
		FALSE,             // 第一个WaitForSingleObject调用线程得到有信号的状态
		NULL);             // 可以用指定互斥量名称

修改为FALSE我们再次运行上面的代码:
在这里插入图片描述

由于子线程第一个调用WaitForSingleObject去获取互斥量的状态。所以子线程变为有锁

我们在来看另一个例子,Mutex的状态是会区分线程。

比如A线程调用WaitForSingleObject获取Mutex得到有信号之后返回那么这个线程后面多次调用都会有信号.除非你调用ReleaseMutex手动释放。

#include <iostream>
#include <Windows.h>
#include<tlhelp32.h>
using namespace std;
#include <string> 

HANDLE ghMutex;

int i = 0;
DWORD WINAPI myRun(
	_In_ LPVOID lpParameter
) {


	cout << "子线程运行" << endl;
	DWORD  dwWaitResult = WaitForSingleObject(
		ghMutex,    // handle to mutex
		INFINITE);  // no time-out interval

	cout << "子线程运行2" << endl;
	  dwWaitResult = WaitForSingleObject(
		ghMutex,    // handle to mutex
		INFINITE);  // no time-out interval
	  cout << "子线程释放锁 " << endl;

	  //子线程手动释放互斥量,那么主线程能得到
	  ReleaseMutex(ghMutex);
	  //子线程由于释放后再次获取,这次由于被主线程获取到互斥量所以会被阻塞
	  dwWaitResult = WaitForSingleObject(
		  ghMutex,    // handle to mutex
		  INFINITE);  // no time-out interval
	  cout << "子线程运行3" << endl;

	cout << "子线程结束 " << endl;

	Sleep(1000);


	//退出代码
	return EXIT_SUCCESS;
}

int main()
{
	ghMutex = CreateMutex(
		NULL,              // 安全属性这里视为空
		FALSE,             // 当前互斥量 是否被创建时的线程持有这个锁,还是得到到第一个WaitForSingleObject调用线程
		NULL);             // 可以用指定互斥量名称

	if (ghMutex == NULL)
	{
		printf("CreateMutex error: %d\\n", GetLastError());
		ReleaseMutex(ghMutex);
		return 1;
	}


	DWORD threadId;

	HANDLE threadHandle = CreateThread(NULL,//是否返回的句柄能让子进程继承
		0,//设置线程栈大小。越小递归深度越深,但是栈帧内存越小,0是默认设置
		myRun,//方法指针,线程运行时会调用这个函数
		NULL,//可传入任意的指针给线程参数
		CREATE_SUSPENDED,//线程启动参数,传入0线程创建时立即运行。CREATE_SUSPENDED会在ResumeThread 才运行
		&threadId//传出 线程id
	);

	ResumeThread(threadHandle);



	//让子线程运行
	Sleep(100);

	cout << "主线程 开始被阻塞" << endl;

	//子线程先获取到互斥量所以主线程无法运行
	 WaitForSingleObject(
		ghMutex,    // handle to mutex
		INFINITE);  // no time-out interval
	 //子线程释放了信号所以继续运行
	cout << "主线程获得信号 设置ghMutex有信号" << endl;
	//让ghMutex变为有信号
	ReleaseMutex(ghMutex);

	

	
	等候线程结束
	WaitForSingleObject(threadHandle, INFINITE);

	cout << "主线程结束 " << i << endl;


	CloseHandle(threadHandle);
	CloseHandle(ghMutex);


	return EXIT_SUCCESS;

}

在这里插入图片描述
Mutext主要用于多线程级别的同步。

Semaphore

信号量,你可以指定多少个对象可以同时调用WaitForSingleObject然后返回.

下面是一个简单的Demo

#include <iostream>
#include <Windows.h>
#include<tlhelp32.h>
#include <string> 

using namespace std;
HANDLE semaphore;


int i = 0;
DWORD WINAPI myRun(
	_In_ LPVOID lpParameter
) {

	DWORD  dwWaitResult = WaitForSingleObject(
		semaphore,    // handle to mutex
		INFINITE);  // no time-out interval

	int i = 5;
	while (--i>0)
	{
		cout << "子线程运行 "<<i << endl;
		Sleep(300);
	}


	cout << "子线程运行 结束" << endl;
	ReleaseSemaphore(semaphore, 1, NULL);
	//退出代码
	return EXIT_SUCCESS;
}

int main()
{
	
	//创建一个信号
	semaphore = CreateSemaphore
	(
		NULL,//
		1, //初始化的时候信号的数量,表示当前最多只能有一个对象调用WaitForSingleObject成功
		2, //最大信号数量,你可以在运行动态增加信号量的数量哦
		NULL//无名信号量
	);

	//失败处理
	if (semaphore == NULL) {
		printf("CreateMutex error: %d\\n", GetLastError());
		ReleaseMutex(semaphore);
		return 1;
	}

	DWORD threadId;

	HANDLE threadHandle = CreateThread(NULL,//是否返回的句柄能让子进程继承
		0,//设置线程栈大小。越小递归深度越深,但是栈帧内存越小,0是默认设置
		myRun,//方法指针,线程运行时会调用这个函数
		NULL,//可传入任意的指针给线程参数
		CREATE_SUSPENDED,//线程启动参数,传入0线程创建时立即运行。CREATE_SUSPENDED会在ResumeThread 才运行
		&threadId//传出 线程id
	);

	ResumeThread(threadHandle);

	//让子线程运行
	Sleep(100);
	
	
	cout << "主线程请求信号量 " << i << endl;
	//由于主线程先获取到一个信号量,而信号量只有一个所以只能等候子线程释放后再继续运行
	DWORD  dwWaitResult = WaitForSingleObject(
		semaphore,    // handle to mutex
		INFINITE);  // no time-out interval
	//子线程释放了锁,所以主线开始运行
	int i = 5;
	while (--i > 0)
	{
		cout << "主线程运行中 " << i << endl;
		Sleep(300);
	}
	
	//释放信号
	ReleaseSemaphore(semaphore, 
		1,//释放多少信号量啊,你可以多次加锁然后一次性的释放
		NULL//一个传出参数可以用于获取之前有多少个信号量
	);

	//等候线程结束
	WaitForSingleObject(threadHandle, INFINITE);

	cout << "主线程结束 " << i << endl;


	CloseHandle(threadHandle);
	CloseHandle(semaphore);

	return EXIT_SUCCESS;
}

在这里插入图片描述

以上是关于win32 相关同步api的主要内容,如果未能解决你的问题,请参考以下文章

UWP是否具有同步文件读/写API?

使用Win32 API实现生产者消费者线程同步

与导出和返回值相关的 ruby​​ win32api 问题

win32API多线程编程

转载使用Win32API实现Windows下异步串口通讯

WIN32 消息Hook API