最近读libuv源码时,发现一个InterlockedCompareExchangePointer的初始化使用例 先讲解下InterlockedCompareExchangePointer这个函数
InterlockedCompareExchangePointer例程执行原子操作,将Destination指向的输入指针值与指针值 Comperand进行比较。
PVOID InterlockedCompareExchangePointer(
[in, out] PVOID volatile *Destination,
[in] PVOID Exchange,
[in] PVOID Comperand
);
指向 PVOID 值的指针。 如果 (*Destination) = Comperand,则例程会将 (*Destination) 设置为 Exchange。
指定要 (*Destination) 设置的 PVOID 值。
指定要与 (*Destination) 进行比较的 PVOID 值。
一个小的测试样例
#include<iostream>
#include<windows.h>
using namespace std;
int main()
{
int a = 1;
int b = 2;
HANDLE p = NULL;
//p = &a; //加上这一行 会输出testEvent == p p != p2
HANDLE p2 = &b;
HANDLE testEvent = InterlockedCompareExchangePointer(&p, p2, NULL);
if (testEvent == NULL)
{
cout << "testEvent == NULL" << endl;
if (p == p2)
{
cout << "p == p2" << endl;
}
}
else if (testEvent == p)
{
cout << "testEvent == p" << endl;
if (p != p2)
{
cout << "p != p2" << endl;
}
}
else if(testEvent == p2)
{
cout << "testEvent == p2" << endl;
}
return 0;
}
简而言之 InterlockedCompareExchangePointer的返回值是其第一个参数的值 同时如果第一个参数 == 第三个参数 第一个参数会获得第二个参数的值,但是返回值仍旧是第一个参数的值
InterlockedCompareExchangePointer在 *Destination(返回指针的原始值,即在例程) 入口处此指针的值。
这是libuv库中的源代码
static void uv__once_inner(uv_once_t* guard, void (*callback)(void)) {
DWORD result;
HANDLE existing_event, created_event;
// 创建一个新的事件对象,用于同步
// CreateEvent 的参数说明:
// - NULL: 使用默认的安全性
// - 1: 表示手动重置事件 (手动重置后需要调用 ResetEvent)
// - 0: 初始状态为非信号状态 (不触发)
// - NULL: 未指定名称 (匿名事件)
created_event = CreateEvent(NULL, 1, 0, NULL);
if (created_event == 0) {
/* 如果事件创建失败,可能是由于内存不足或其他资源限制,调用错误处理函数 */
uv_fatal_error(GetLastError(), "CreateEvent");
}
// 尝试将 guard->event 从 NULL 更新为 created_event
// - 如果 guard->event 之前是 NULL,则 InterlockedCompareExchangePointer 返回 NULL
// - 如果 guard->event 已经被其他线程设置,则返回现有的事件句柄
existing_event = InterlockedCompareExchangePointer(&guard->event,
created_event,
NULL);
if (existing_event == NULL) {
/* 如果 existing_event 是 NULL,表示当前线程“赢得了竞争”,是第一个进入的线程 */
// 调用 callback 函数,执行一次性初始化
callback();
// 将事件设为信号状态,唤醒所有等待的线程
result = SetEvent(created_event);
assert(result); // 确保 SetEvent 成功
// 将 guard->ran 标记为 1,表示初始化已完成
guard->ran = 1;
} else {
/* 如果 existing_event 不是 NULL,表示当前线程“输了竞争” */
// 销毁当前线程创建的事件对象,因为另一个线程的事件对象已被使用
CloseHandle(created_event);
// 等待 `existing_event` 事件对象变为信号状态
// 等待时间无限长,直到另一个线程将事件设为信号状态
result = WaitForSingleObject(existing_event, INFINITE);
assert(result == WAIT_OBJECT_0); // 确保等待操作成功
}
}
很明显这个函数实现了类似
C++stl库中引入的std::call_once的功能,都为了保证某个初始化函数(callback())只执行一次,且只有一个线程可以执行,其他线程必须等待初始化完成。
因此我们可以简单仿照着实现一个windows平台下类似call_once的函数
#include <windows.h>
#include <stdio.h>
#include <assert.h>
typedef struct {
HANDLE event; // 用于同步的事件句柄
volatile LONG ran; // 标记是否已初始化
} MyOnceFlag;
#define MY_ONCE_FLAG_INIT { NULL, 0 }
// 函数声明
void MyCall_Once(MyOnceFlag* flag, void (*callback)(void));
// 测试函数:将作为回调函数使用
void initialize_once() {
printf("Initializing once by thread %lu\n", GetCurrentThreadId());
Sleep(100); // 模拟耗时的初始化工作
}
// 确保 callback 只执行一次
void MyCall_Once(MyOnceFlag* flag, void (*callback)(void)) {
HANDLE created_event;
HANDLE existing_event;
// 1. 创建一个新的事件对象
created_event = CreateEvent(NULL, TRUE, FALSE, NULL); // 手动重置,初始非信号状态
if (created_event == NULL) {
fprintf(stderr, "Failed to create event. Error: %lu\n", GetLastError());
return;
}
// 2. 使用原子操作尝试将事件句柄存入 flag->event
existing_event = InterlockedCompareExchangePointer(&flag->event, created_event, NULL);
if (existing_event == NULL) {
// 当前线程赢得了竞争,执行初始化
callback();
// 设置事件为信号状态,唤醒其他等待线程
SetEvent(created_event);
flag->ran = 1;
} else {
// 其他线程已创建事件对象,关闭自己创建的对象
CloseHandle(created_event);
// 等待 `existing_event` 变为信号状态,确保初始化完成
WaitForSingleObject(existing_event, INFINITE);
}
}
// 线程入口函数:每个线程都会调用 MyCall_Once
DWORD WINAPI thread_func(LPVOID param) {
MyOnceFlag* flag = (MyOnceFlag*)param;
// 每个线程尝试调用 MyCall_Once
MyCall_Once(flag, initialize_once);
return 0;
}
int main() {
// 初始化 once_flag
MyOnceFlag flag = MY_ONCE_FLAG_INIT;
// 创建多个线程
const int NUM_THREADS = 5;
HANDLE threads[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; ++i) {
threads[i] = CreateThread(
NULL, // 默认安全属性
0, // 默认栈大小
thread_func, // 线程入口函数
&flag, // 传递的参数
0, // 默认创建标志
NULL // 忽略线程ID
);
}
// 等待所有线程完成
WaitForMultipleObjects(NUM_THREADS, threads, TRUE, INFINITE);
// 关闭线程句柄
for (int i = 0; i < NUM_THREADS; ++i) {
CloseHandle(threads[i]);
}
printf("All threads finished.\n");
return 0;
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。