C语言: 多线程(3)-事件和互斥量

事件(信号)创建函数:

CreateEventA函数:创建或打开一个命名或未命名的事件对象。要为对象指定访问掩码,请使用CreateEventEx函数。

函数原型:

HANDLE CreateEventA(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCSTR                lpName
);

参数说明:

  • lpEventAttributes :指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则子进程不能继承该句柄。结构的lpSecurityDescriptor成员为新事件指定一个 安全描述符。如果lpEventAttributesNULL或0,则事件获取默认的安全描述符。事件的默认安全描述符中的ACL来自创建者的主要或模拟令牌。
  • bManualReset:如果此参数为TRUE,则该函数创建一个手动重置事件对象,该对象需要使用 ResetEvent函数将事件状态设置为非信号状态。如果此参数为FALSE,则该函数将创建一个自动重置事件对象,并且在释放单个等待线程之后,系统会自动将事件状态重置为无信号。
  • bInitialState:如果此参数为TRUE,则表示事件对象的初始状态;否则,将通知事件对象。否则,它是无信号的。
  • lpName:事件对象的名称。名称不得超过 MAX_PATH个字符。名称比较区分大小写。

如果lpName与现有命名事件对象的名称匹配,则此函数请求EVENT_ALL_ACCESS访问权限。在这种情况下,bManualResetbInitialState参数将被忽略,因为它们已由创建过程设置。如果 lpEventAttributes参数不为NULL,则它确定是否可以继承该句柄,但是将忽略其安全描述符成员。

如果lpNameNULL,则创建事件对象时不使用名称。

如果lpName与同一名称空间中另一种对象的名称匹配(例如现有的信号量,互斥量,可等待的计时器,作业或文件映射对象),则该函数将失败,并且 GetLastError函数将返回 ERROR_INVALID_HANDLE。发生这种情况是因为这些对象共享相同的名称空间。

该名称可以具有“全局”或“本地”前缀,以在全局或会话名称空间中显式创建对象。名称的其余部分可以包含除反斜杠字符()之外的任何字符。有关更多信息,请参见内核对象命名空间。使用终端服务会话可实现快速的用户切换。内核对象名称必须遵循为“终端服务”概述的准则,以便应用程序可以支持多个用户。

返回值:

如果函数成功,则返回值是事件对象的句柄。如果命名事件对象在函数调用之前存在,则该函数返回现有对象的句柄,而 GetLastError返回 ERROR_ALREADY_EXISTS

如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用 GetLastError

说明:

CreateEvent返回的句柄具有 EVENT_ALL_ACCESS访问权限;如果调用者已被授予访问权限,则它可用于需要事件对象句柄的任何函数中。如果事件是通过模拟另一个用户的服务或线程创建的,则可以在创建事件时将安全描述符应用于事件,也可以通过更改事件的默认DACL来更改创建过程的默认安全描述符。

调用过程的任何线程都可以在对其中一个wait函数的调用中指定事件对象句柄 。当用信号通知指定对象的状态时,将返回单对象等待函数。可以指示多对象等待函数在任何一个或所有指定对象发出信号时返回。当等待函数返回时,等待线程被释放以继续执行。

事件对象的初始状态由bInitialState参数指定。使用SetEvent函数将事件对象的状态设置为已信号通知。使用ResetEvent函数将事件对象的状态重置为非信号状态。

当发出手动复位事件对象的状态信号时,它将保持信号状态,直到由ResetEvent函数将其显式复位为未信号状态为止。可以在发出信号通知对象状态的同时释放任何数量的等待线程,或随后开始对指定事件对象进行等待操作的线程。

当发出自动重置事件对象的状态信号时,它将一直发出信号,直到释放单个等待线程为止;然后系统会自动将状态重置为无信号。如果没有线程在等待,则事件对象的状态保持信号状态。

多个进程可以具有同一事件对象的句柄,从而可以将该对象用于进程间同步。可以使用以下对象共享机制:

  • 如果CreateEventlpEventAttributes参数 启用了继承,则CreateProcess函数创建的子进程可以继承事件对象的句柄
  • 进程可以在对DuplicateHandle函数的调用中指定事件对象句柄, 以创建可被另一个进程使用的重复句柄。
  • 进程可以在对OpenEventCreateEvent函数的调用中指定事件对象的名称 。

使用CloseHandle函数关闭句柄。进程终止时,系统会自动关闭句柄。当事件对象的最后一个句柄关闭时,该事件对象将被销毁。

示例1:顺序执行函数

#include<stdio.h>
#include<stdlib.h>
#include<process.h>//线程头文件
#include<Windows.h>

HANDLE event[3] = { 0 };//事件句柄
HANDLE therad[3] = { 0 };//线程句柄

DWORD WINAPI funOne(void* vPtr)
{
	MessageBoxA(0, "信息1", "窗口1", 0);
	printf("第1个线程执行函数完毕\n");
	SetEvent(event[0]);//发出信号
	return 0;
}

DWORD WINAPI funTow(void* vPtr)
{
	WaitForSingleObject(event[0], INFINITE);//等待事件1信号
	MessageBoxA(0, "信息2", "窗口2", 0);
	printf("第2个线程执行函数完毕\n");
	return 0;
}


void main()
{
	//创建两个事件
	event[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
	event[1] = CreateEvent(NULL, TRUE, FALSE, NULL);

	//创建两个线程
	therad[0]=CreateThread(NULL, 0, funOne, NULL, 0, NULL);
	therad[1]=CreateThread(NULL, 0, funTow, NULL, 0, NULL);

	WaitForMultipleObjects(2, therad, TRUE, INFINITE);

	printf("所有线程执行完毕\n");
	system("pause");
}

示例2:交替执行函数

#include<stdio.h>
#include<stdlib.h>
#include<process.h>//线程头文件
#include<Windows.h>

HANDLE event[3] = { 0 };//事件句柄
HANDLE therad[3] = { 0 };//线程句柄

int num = 0;

DWORD WINAPI fun1(void* vPtr)
{
	int i = 1;
	printf("函数1 第【%d】次执行!\n", i);
	SetEvent(event[0]);//发出信号 0 ,必须有一个先发出信号,否则死锁
	
	while (++i)
	{
		WaitForSingleObject(event[1], INFINITE);//等待信号 1
		printf("函数1 第【%d】次执行!\n", i);
		Sleep(1000);
		SetEvent(event[0]);//发出信号 0
	}
	return 0;
}

DWORD WINAPI fun2(void* vPtr)
{
	int i = 0;
	while (++i)
	{
		WaitForSingleObject(event[0], INFINITE);//等待信号 0
		printf("函数2 第【%d】次执行!\n", i);
		Sleep(1000);
		SetEvent(event[1]);//发出信号 1
	}
	return 0;
}

void main()
{
	//创建两个事件
	event[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
	event[1] = CreateEvent(NULL, TRUE, FALSE, NULL);

	//创建两个线程分别执行两个函数
	therad[0] = CreateThread(NULL, 0, fun1, NULL, 0, NULL);
	therad[1] = CreateThread(NULL, 0, fun2, NULL, 0, NULL);

	//等待线程全部结束
	WaitForMultipleObjects(2, therad, TRUE, INFINITE);

	system("pause");
}

事件的中介者模式:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<process.h>//线程头文件
#include<Windows.h>
#include<memory.h>
#define N 3
#define M 4
#define COUNT 1024

HANDLE event[M] = { 0 };//事件句柄
HANDLE therad[N] = { 0 };//线程句柄
char msg[COUNT] = { 0 };//公共变量	
CRITICAL_SECTION cs; //声明临界区结构体

//中介者,传递消息该表信号
DWORD WINAPI mediator(void* vPtr)
{
	int i = 0, k = 0;
	while (++i)
	{
		
		if (k == 0)
		{
			WaitForSingleObject(event[1], INFINITE);//等待信号 1
			
			printf(msg);
			Sleep(1000);
			SetEvent(event[2]);//发出信号 2
			k = 1;
		}	
		else
		{
			WaitForSingleObject(event[3], INFINITE);//等待信号 3
			
			printf(msg);
			Sleep(1000);
			SetEvent(event[0]);//发出信号 0
			k = 0;
		}
		if (i % 2 == 0)
		{
			printf("\n");
		}
	}

	return 0;
}

//发送
DWORD WINAPI fun_send(void* vPtr)
{
	int i = 1;
	EnterCriticalSection(&cs);//进入临界区
	memset(msg, '0', COUNT);
	sprintf(msg, "SEND: 第 【%d】次发送消息!\n", i);
	LeaveCriticalSection(&cs);//退出临界区
	Sleep(1000);
	SetEvent(event[1]);
	while (++i)
	{
		WaitForSingleObject(event[0], INFINITE);
		EnterCriticalSection(&cs);//进入临界区
		memset(msg, '0', COUNT);
		sprintf(msg, "SEND: 第 【%d】次发送消息!\n", i);
		LeaveCriticalSection(&cs);//退出临界区
		Sleep(1000);
		SetEvent(event[1]);
	}
	return 0;
}

//接受
DWORD WINAPI fun_receive(void* vPtr)
{
	int i = 0;
	while (++i)
	{
		WaitForSingleObject(event[2], INFINITE);
		EnterCriticalSection(&cs);//进入临界区
		memset(msg, '0', COUNT);//清空字符串内容
		sprintf(msg, "RECEIVE: 第 【%d】次接受消息!\n", i);
		LeaveCriticalSection(&cs);//退出临界区
		Sleep(1000);
		SetEvent(event[3]);
	}

	return 0;
}

void main()
{
	InitializeCriticalSection(&cs);//初始化临界区

	//创建三个事件 第二个参数设置为  FALSE 让其自动复位。第四参数为事件名称
	event[0] = CreateEventA(NULL, FALSE, FALSE, "function_name1");
	event[1] = CreateEventA(NULL, FALSE, FALSE, "function_name2");
	event[2] = CreateEventA(NULL, FALSE, FALSE, "function_name3");
	event[3] = CreateEventA(NULL, FALSE, FALSE, "function_name4");

	//创建3个线程分别执行3个函数
	therad[0] = CreateThread(NULL, 0, mediator, NULL, 0, NULL);
	therad[1] = CreateThread(NULL, 0, fun_send, NULL, 0, NULL);
	therad[2] = CreateThread(NULL, 0, fun_receive, NULL, 0, NULL);

	WaitForMultipleObjects(N, therad, TRUE, INFINITE);

	DeleteCriticalSection(&cs);//释放CRITICAL_SECTION结构指针
	system("pause");
}

 

互斥量:

CreateMutex用法详解:创建或打开一个已命名或未命名的互斥对象。要为对象指定访问掩码,请使用CreateMutexEx函数。

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针
BOOL bInitialOwner, // 初始化互斥对象的所有者
LPCTSTR lpName // 指向互斥对象名的指针
);

参数:

  1. lpMutexAttributes:指向SECURITY_ATTRIBUTES结构的指针 。如果此参数为NULL,则子进程不能继承该句柄。该结构的lpSecurityDescriptor成员为新的互斥锁指定一个安全描述符。如果lpMutexAttributesNULL,则互斥锁将获取默认的安全描述符。互斥锁的默认安全描述符中的ACL来自创建者的主令牌或模拟令牌。
  2. bInitialOwner:如果此值为TRUE,并且调用方创建了互斥对象,则调用线程将获得互斥对象的初始所有权。否则,调用线程将无法获得互斥锁的所有权。要确定呼叫者是否创建了互斥锁,请参见“返回值”部分。
  3. lpName:互斥对象的名称。名称不得超过MAX_PATH个字符。名称比较区分大小写。如果lpName与现有的已命名互斥对象的名称匹配,则此函数请求MUTEX_ALL_ACCESS访问权限。在这种情况下,bInitialOwner参数将被忽略,因为它已由创建过程设置。如果lpMutexAttributes参数不为NULL,则它确定是否可以继承该句柄,但是将忽略其安全性描述符成员。如果lpNameNULL,则创建的互斥对象不带名称。如果lpName与现有事件,信号量,可等待计时器,作业或文件映射对象的名称匹配,则该函数将失败,并且GetLastError函数将返回ERROR_INVALID_HANDLE。发生这种情况是因为这些对象共享相同的名称空间。

返回值:

如果函数成功,则返回值是新创建的互斥对象的句柄。如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用GetLastError

如果互斥锁是一个命名的互斥锁,并且该对象在此函数调用之前就已存在,则返回值是现有对象OpenMutex a>函数的句柄 。

说明:

CreateMutex返回的句柄 具有MUTEX_ALL_ACCESS访问权限;它可以用于需要互斥对象的句柄的任何函数中,前提是已授予调用方访问权限。如果互斥锁是通过模拟其他用户的服务或线程创建的,则可以在创建互斥锁时将安全描述符应用到该互斥锁,也可以通过更改其默认DACL来更改创建过程的默认安全描述符。

如果您使用命名的互斥锁将应用程序限制为单个实例,则恶意用户可以在创建此互斥锁之前先创建该互斥锁,并阻止应用程序启动。为避免这种情况,请创建一个随机命名的互斥对象并存储该名称,以便只能由授权用户获得。或者,您可以使用文件来实现此目的。要将您的应用程序限制为每个用户一个实例,请在用户的配置文件目录中创建一个锁定文件。

调用进程的任何线程都可以在对其中一个wait函数的调用中指定互斥对象句柄 。当用信号通知指定对象的状态时,将返回单对象等待函数。可以指示多对象等待函数在任何一个或所有指定对象发出信号时返回。当等待函数返回时,等待线程被释放以继续执行。

当互斥对象不属于任何线程时,会发出状态通知。创建线程可以使用bInitialOwner标志来请求互斥量的立即所有权。否则,线程必须使用等待功能之一来请求所有权。当发出互斥锁状态的信号时,一个等待线程被授予所有权,互斥锁的状态变为无信号,然后等待函数返回。在任何给定时间,只有一个线程可以拥有互斥量。拥有线程使用ReleaseMutex函数释放其所有权。

拥有互斥锁的线程可以在重复的等待函数调用中指定相同的互斥锁,而不会阻止其执行。通常,您不会重复等待相同的互斥锁,但是这种机制可以防止线程在等待已拥有的互斥锁时使其自身死锁。但是,要释放其所有权,线程必须在互斥体每次等待时都调用 一次ReleaseMutex

两个或更多进程可以调用 CreateMutex创建相同的命名互斥体。第一个进程实际上创建了互斥锁,具有足够访问权限的后续进程仅打开了现有互斥锁的句柄。这使多个进程能够获得同一个互斥锁的句柄,同时使用户不必承担确保首先启动创建进程的责任。使用此技术时,应将bInitialOwner标志设置为FALSE。否则,可能很难确定哪个进程具有初始所有权。

多个进程可以具有同一互斥对象的句柄,从而可以将该对象用于进程间同步。可以使用以下对象共享机制:

  • 如果CreateMutexlpMutexAttributes参数 启用了继承,则CreateProcess函数创建的子进程 可以继承互斥对象的句柄。此机制适用于已命名和未命名的互斥体。
  • 一个进程可以在对DuplicateHandle函数的调用中指定互斥对象的句柄,以创建一个可被另一个进程使用的重复句柄。此机制适用于已命名和未命名的互斥体。
  • 进程可以在对[OpenMutex](/ windows / win32 / api / synchapi / nf-synchapi-openmutexw)a>或CreateMutex函数的调用中指定一个命名的互斥锁, 以检索该互斥对象的句柄。

使用CloseHandle函数关闭句柄。进程终止时,系统会自动关闭句柄。当其最后一个句柄关闭时,互斥对象将被销毁。

示例:

#include<stdio.h>
#include<stdlib.h>
#include<process.h>//线程头文件
#include<Windows.h>

#define N 10
int num = 0;
HANDLE mutex = NULL; //指针


DWORD WINAPI add(void* vPtr)
{
	WaitForSingleObject(mutex, INFINITE);//等待互斥量
	for (int i = 0; i < 100000; i++)//多线程操作相同变量,使用信号互斥量防止冲突
	{
		num++;
	}
	ReleaseMutex(mutex);//释放互斥量
	return 0;
}


void main()
{
	mutex = CreateMutex(NULL, FALSE, NULL);//创建互斥量

	HANDLE hd[10];

	for (int i = 0; i < N; i++)
	{
		hd[i] = CreateThread(NULL, 0, add, NULL, 0, NULL);
	}

	WaitForMultipleObjects(N, hd, TRUE, INFINITE);

	printf("num=%d\n", num);

	if (mutex != 0)
	{
		CloseHandle(mutex);//销毁互斥量
	}
	
	system("pause");
}

示例:

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

#define THREADCOUNT 2

HANDLE ghMutex;

DWORD WINAPI WriteToDatabase(LPVOID);

int main(void)
{
	HANDLE aThread[THREADCOUNT];
	DWORD ThreadID;
	int i;

	// 创建一个没有初始所有者的互斥锁

	ghMutex = CreateMutex(
		NULL,              // 默认的安全属性
		FALSE,             // initially not owned
		NULL);             // unnamed mutex

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

	// 创建工作线程

	for (i = 0; i < THREADCOUNT; i++)
	{
		aThread[i] = CreateThread(
			NULL,       // default security attributes
			0,          // default stack size
			(LPTHREAD_START_ROUTINE)WriteToDatabase,
			NULL,       // no thread function arguments
			0,          // default creation flags
			&ThreadID); // receive thread identifier

		if (aThread[i] == NULL)
		{
			printf("CreateThread error: %d\n", GetLastError());
			return 1;
		}
	}

	// Wait for all threads to terminate

	WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

	// Close thread and mutex handles

	for (i = 0; i < THREADCOUNT; i++)
	{
		if (aThread[i] != 0)
		{
			CloseHandle(aThread[i]);
		}
	}
		

	CloseHandle(ghMutex);

	system("pause");

	return 0;
}

DWORD WINAPI WriteToDatabase(LPVOID lpParam)
{
	// lpParam not used in this example
	UNREFERENCED_PARAMETER(lpParam);

	DWORD dwCount = 0, dwWaitResult;

	// 请求互斥锁的所有权。

	while (dwCount < 20)
	{
		dwWaitResult = WaitForSingleObject(
			ghMutex,    // handle to mutex
			INFINITE);  // no time-out interval

		switch (dwWaitResult)
		{
			// 线程获得了互斥锁的所有权
		case WAIT_OBJECT_0:
			__try {
				// TODO: Write to the database
				printf("Thread %d writing to database...\n",
					GetCurrentThreadId());
				dwCount++;
			}

			__finally {
				// Release ownership of the mutex object
				if (!ReleaseMutex(ghMutex))
				{
					// Handle error.
				}
			}
			break;

			// 线程拥有一个废弃的互斥锁
			//数据库处于不确定状态
		case WAIT_ABANDONED:
			return FALSE;
		}
	}
	return TRUE;
}

 

详细说明参见:互斥量

 

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值