You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DirectX跨线程传递纹理时的数据竞争问题排查

DirectX多线程数据竞争与跨设备资源访问问题修复

没错,你必须添加线程同步机制,而且你的代码里还有几个关键问题在放大这个随机异常,咱们一步步理清楚:

一、为什么一定要做线程同步?

即使你暂时没调用Release(),生产者线程返回纹理指针时,DirectX的异步操作很可能还没完全收尾:

  • 你调用了immediate_context->Flush(),但这个方法只是确保命令队列提交给了GPU,不代表GPU已经执行完所有纹理拷贝命令。如果消费者线程在GPU还没完成纹理初始化就去访问,必然会触发无效资源的异常。
  • 跨线程传递资源指针本身就存在竞争窗口:生产者可能还没完成纹理的封装工作,消费者就已经拿到指针并尝试使用,导致访问未完全初始化的资源。

加sleep(1000)能“解决”问题只是碰运气——刚好让GPU在消费者访问前完成了操作,但这完全不可靠,必须用正经的同步机制(比如事件、临界区、信号量)来确保消费者只在生产者确认资源可用后才开始操作。

二、你忽略的致命问题:跨Device资源访问不合法

你提到生产者和消费者各自在同一适配器上创建了独立的ID3D11Device和ID3D11DeviceContext,但直接传递ID3D11Texture2D*指针在两个Device间使用是完全不允许的!

DirectX 11中,资源和创建它的Device是绑定的,不能在另一个Device的上下文里直接操作。如果要在多个Device(哪怕是同一适配器的)间共享资源,必须用DirectX的共享资源机制:

修正生产者的纹理创建代码(支持共享)

dxgi_dd->AcquireNextFrame(INFINITE, &frame_info, &desktop_resource);
ID3D11Texture2D *desktop_texture;
desktop_resource->QueryInterface(__uuidof(ID3D11Texture2D), (void **)&desktop_texture);
desktop_resource->Release();

D3D11_TEXTURE2D_DESC texture_desc;
memset(&texture_desc, 0, sizeof(texture_desc));
texture_desc.Width = desktop_desc.Width;
texture_desc.Height = desktop_desc.Height;
texture_desc.MipLevels = 1;
texture_desc.ArraySize = 1;
texture_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
texture_desc.SampleDesc.Count = 1;
texture_desc.SampleDesc.Quality = 0;
texture_desc.Usage = D3D11_USAGE_DEFAULT;
texture_desc.BindFlags = 0;
texture_desc.CPUAccessFlags = 0;
// 关键:添加共享资源标记
texture_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;

d3d11_device->CreateTexture2D(&texture_desc, NULL, &return_texture);
immediate_context->CopyResource(return_texture, desktop_texture);
immediate_context->Flush();

// 获取共享句柄(需要转为IDXGIResource)
IDXGIResource* dxgi_resource = nullptr;
return_texture->QueryInterface(__uuidof(IDXGIResource), (void**)&dxgi_resource);
HANDLE shared_handle = nullptr;
dxgi_resource->GetSharedHandle(&shared_handle);
dxgi_resource->Release();

dxgi_dd->ReleaseFrame();
desktop_texture->Release();
// 返回共享句柄+纹理尺寸,而不是直接返回纹理指针
return encapsulate(shared_handle, desktop_desc.Width, desktop_desc.Height);

修正消费者的资源访问代码(通过共享句柄打开资源)

ID3D11Texture2D *dx_input_texture;
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
desc.Width = received_texture.width;
desc.Height = received_texture.height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
desc.CPUAccessFlags = 0;
d3d11_device->CreateTexture2D(&desc, NULL, &dx_input_texture);

// 通过共享句柄打开生产者创建的纹理
ID3D11Texture2D* shared_texture = nullptr;
d3d11_device->OpenSharedResource(received_texture.shared_handle, __uuidof(ID3D11Texture2D), (void**)&shared_texture);

immediate_context->CopyResource(dx_input_texture, shared_texture);
immediate_context->Flush();

// 使用dx_input_texture...

// 记得释放共享纹理
shared_texture->Release();

三、添加线程同步的具体实现

比如用Windows事件来同步生产者和消费者:

  1. 先创建一个全局/线程间可见的事件:
// 手动重置事件,初始为未触发状态
HANDLE g_texture_ready_event = CreateEvent(NULL, TRUE, FALSE, NULL);
  1. 生产者线程完成纹理创建和共享句柄获取后,触发事件:
// 生产者线程最后一步
SetEvent(g_texture_ready_event);
  1. 消费者线程先等待事件触发,再处理资源:
// 消费者线程开头
WaitForSingleObject(g_texture_ready_event, INFINITE);
// 重置事件,等待下一次生产
ResetEvent(g_texture_ready_event);

// 之后再执行打开共享资源、拷贝等操作

总结

你的随机异常是数据竞争+跨Device资源非法访问共同导致的,必须同时解决这两个问题:

  • 用线程同步机制确保消费者只在资源完全可用后访问;
  • 用DirectX共享资源机制在两个独立Device间安全传递纹理。

内容的提问来源于stack exchange,提问作者Dean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:00:45