Win32编程之线程池同步(十三)

发布时间 2023-09-19 17:09:18作者: TechNomad

1.InterlockedAdd函数

InterlockedAdd 是 Windows API 中的一个原子操作函数,用于在多线程环境下对一个变量执行原子加法操作。原子操作是指在执行期间不会被其他线程中断,从而确保多线程环境下的数据一致性。

函数原型:

LONG InterlockedAdd(
  LONG volatile *Addend,
  LONG Value
);

参数解释:

  • Addend:一个指向被加数的 LONG 类型指针。这是要修改的变量的地址。
  • Value:要加到变量上的值。

返回值:

InterlockedAdd 函数返回 Addend 参数原始值的拷贝,即执行加法操作前的值。

函数功能:

InterlockedAdd 函数的作用是将 Addend 指针指向的变量的值与 Value 相加,并将结果存储在该变量中。这个操作是原子的,意味着在执行加法操作的过程中,不会被其他线程中断,从而确保多线程环境下的数据一致性。它适用于实现各种线程间的计数和累加操作。

示例代码:

#include <Windows.h>
#include <stdio.h>

LONG g_count = 0;

void CALLBACK ThreadFunc(PTP_CALLBACK_INSTANCE pInstance, void* p, PTP_WORK pWork)
{
	for (int i = 0; i < 200; i++) {
		InterlockedAdd(&g_count, 1);
	}
}

int main() {
	PTP_WORK pwk = CreateThreadpoolWork((PTP_WORK_CALLBACK)ThreadFunc, NULL, NULL);
	for (int i = 0; i < 100000; i++) {
		SubmitThreadpoolWork(pwk);
	}

	WaitForThreadpoolWorkCallbacks(pwk, FALSE);

	CloseThreadpoolWork(pwk);

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

	system("pause");

	return 1;
}

二、设置临界区

在 Windows 操作系统中,可以使用临界区(Critical Section)来实现线程间的互斥访问,确保多个线程不会同时访问共享资源,从而避免竞态条件和数据不一致性问题。 

以下是如何在 Windows 中定义和使用临界区的基本步骤:

步骤 1:定义临界区变量

首先,需要定义一个 CRITICAL_SECTION 结构体变量,用于表示临界区对象。通常,将其定义为全局或局部变量,以确保多个线程都可以访问。 

CRITICAL_SECTION gCriticalSection; // 全局临界区变量

步骤 2:初始化临界区

在使用临界区之前,必须对其进行初始化。可以使用 InitializeCriticalSection 函数进行初始化。

InitializeCriticalSection(&gCriticalSection);

步骤 3:进入临界区

要进入临界区(获取临界区锁),使用 EnterCriticalSection 函数。一旦线程进入临界区,其他线程将被阻塞,直到该线程退出临界区。

EnterCriticalSection(&gCriticalSection);

步骤 4:执行临界区代码

在进入临界区后,可以执行需要互斥访问的代码段,例如修改共享资源的操作。

步骤 5:离开临界区

要离开临界区(释放临界区锁),使用 LeaveCriticalSection 函数。

LeaveCriticalSection(&gCriticalSection);

步骤 6:销毁临界区

在不再需要使用临界区时,应该对其进行清理,释放相关资源。可以使用 DeleteCriticalSection 函数来销毁临界区对象。

DeleteCriticalSection(&gCriticalSection);

示例代码: 

#include <Windows.h>
#include <stdio.h>

LONG g_count = 0;

void CALLBACK ThreadFunc(PTP_CALLBACK_INSTANCE pInstance, void* p, PTP_WORK pWork)
{	
	EnterCriticalSection((CRITICAL_SECTION*)p);

	for (int i = 0; i < 200; i++) {
		InterlockedAdd(&g_count, 1);
	}

	//LeaveCriticalSection((CRITICAL_SECTION*)p);
	LeaveCriticalSectionWhenCallbackReturns(pInstance, (CRITICAL_SECTION*)p);
}

int main() {
	CRITICAL_SECTION cs;
	InitializeCriticalSection(&cs);

	PTP_WORK pwk = CreateThreadpoolWork((PTP_WORK_CALLBACK)ThreadFunc, &cs, NULL);
	for (int i = 0; i < 100000; i++) {
		SubmitThreadpoolWork(pwk);
	}

	WaitForThreadpoolWorkCallbacks(pwk, FALSE);
	DeleteCriticalSection(&cs);

	CloseThreadpoolWork(pwk);

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

	system("pause");

	return 1;
}

LeaveCriticalSection 函数和 LeaveCriticalSectionWhenCallbackReturns 函数都用于释放临界区锁(退出临界区),但它们的使用场景和行为略有不同:

LeaveCriticalSection 函数:

  • LeaveCriticalSection 函数用于在普通的代码路径中手动退出临界区。这意味着,在临界区中的任何线程都可以调用 LeaveCriticalSection 来释放临界区锁。

  • 调用 LeaveCriticalSection 会立即释放临界区锁,使得其他线程有机会进入该临界区。

  • 这个函数通常用于手动管理临界区的锁定和解锁操作。

示例用法:

EnterCriticalSection(&gCriticalSection);
// 执行需要互斥访问的操作
LeaveCriticalSection(&gCriticalSection);

LeaveCriticalSectionWhenCallbackReturns 函数:

  • LeaveCriticalSectionWhenCallbackReturns 函数通常用于在回调函数中退出临界区。回调函数是通过线程池或异步 I/O 操作注册的,当操作完成时会自动调用。

  • 这个函数的主要目的是确保在回调函数执行完毕后才离开临界区。这对于在回调函数中对共享资源进行安全访问非常有用。

  • 使用 LeaveCriticalSectionWhenCallbackReturns 可以确保在回调函数执行期间,其他线程不会进入相同的临界区。

示例用法(在回调函数中):

// 在异步操作的回调函数中
LeaveCriticalSectionWhenCallbackReturns(&gCriticalSection, callbackContext);

总之,LeaveCriticalSection 用于手动管理临界区的锁定和解锁,而 LeaveCriticalSectionWhenCallbackReturns 通常用于在回调函数中确保临界区锁的安全释放。它们的选择取决于你的代码逻辑和使用场景。