前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Windows下的原子函数InterlockedCompareExchangePointer函数使用例-实现windows下的std::call_once

Windows下的原子函数InterlockedCompareExchangePointer函数使用例-实现windows下的std::call_once

原创
作者头像
晨星成焰
发布2024-10-28 17:30:52
1090
发布2024-10-28 17:30:52
举报
文章被收录于专栏:C++入门基础知识

最近读libuv源码时,发现一个InterlockedCompareExchangePointer的初始化使用例 先讲解下InterlockedCompareExchangePointer这个函数

InterlockedCompareExchangePointer例程执行原子操作,将Destination指向的输入指针值与指针值 Comperand进行比较。

代码语言:javascript
复制
PVOID InterlockedCompareExchangePointer(
  [in, out] PVOID volatile *Destination,
  [in]      PVOID          Exchange,
  [in]      PVOID          Comperand
);
  • [in, out] Destination

指向 PVOID 值的指针。 如果 (*Destination) = Comperand,则例程会将 (*Destination) 设置为 Exchange

  • [in] Exchange

指定要 (*Destination) 设置的 PVOID 值。

  • [in] Comperand

指定要与 (*Destination) 进行比较的 PVOID 值。

一个小的测试样例

代码语言:cpp
复制
#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库中的源代码

代码语言:c
复制
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的函数

代码语言:c
复制
#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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档