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事件来同步生产者和消费者:
- 先创建一个全局/线程间可见的事件:
// 手动重置事件,初始为未触发状态 HANDLE g_texture_ready_event = CreateEvent(NULL, TRUE, FALSE, NULL);
- 生产者线程完成纹理创建和共享句柄获取后,触发事件:
// 生产者线程最后一步 SetEvent(g_texture_ready_event);
- 消费者线程先等待事件触发,再处理资源:
// 消费者线程开头 WaitForSingleObject(g_texture_ready_event, INFINITE); // 重置事件,等待下一次生产 ResetEvent(g_texture_ready_event); // 之后再执行打开共享资源、拷贝等操作
总结
你的随机异常是数据竞争+跨Device资源非法访问共同导致的,必须同时解决这两个问题:
- 用线程同步机制确保消费者只在资源完全可用后访问;
- 用DirectX共享资源机制在两个独立Device间安全传递纹理。
内容的提问来源于stack exchange,提问作者Dean
相关产品推荐
相关产品推荐

