我正在通过带有DirectX 9的HLSL Pixel Shaders处理视频帧数据。我想通过命令缓冲区连续运行所有操作。一旦基本版本正常工作(逐个运行命令),则执行以下命令将返回先前生成的帧的结果,因为DirectX9存在1帧延迟,因此GPU在前一帧上工作我们正在填写当前的帧数据。
如果我用一个线程运行4个着色器,我得不到链中下一个着色器的帧,直到我得到第一个着色器的结果,所以获得正确输出的唯一方法是让DX9渲染一个虚拟场景,将性能降低一半。但是,当在8个线程上运行时,我应该能够拥有一个包含多达8个命令的命令缓冲区,这些命令可以一个接一个地链接。现在我还在逐个处理它们(还没有链接输出),而且它没有按我想要的方式工作。
有各种奇怪的多线程问题。
我在8个线程上运行4个着色器,这导致32个类调用AddCommandToChain。
首先,表现非常缓慢。如果为每个类实例启动ProcessFrames,那么性能要高得多;但后来我最终拥有32台占用大量内存的DirectX设备。理论上,最终版本应该提供比使用32个设备运行时性能更好的两倍。
其次,StartWorkerThread有时会被调用两次。似乎有一种我无法解决的竞争条件。
第三,有时候代码工作正常,有时它会死锁,有时候会随机冻结一段时间。
第四,我添加了一堆互斥锁并且它以这种方式工作,但如果我删除其中一些锁,那么我开始得到各种奇怪的行为。
如何“修复”此代码以使其以可接受的方式工作?
#include <list>
#include <thread>
#include <mutex>
#include <concurrent_queue.h>
using namespace concurrency;
static int threadCount;
static concurrent_queue<CommandStruct> cmdBuffer;
static std::mutex initLock, startLock, waiterLock;
static std::thread* WorkerThread;
static HANDLE WorkerWaiting;
static void AddCommandToQueue(CommandStruct* cmd, IScriptEnvironment* env);
static void StartWorkerThread(IScriptEnvironment* env);## Heading ##
int Shader::threadCount = 0;
concurrent_queue<CommandStruct> Shader::cmdBuffer;
std::mutex Shader::startLock, Shader::initLock, Shader::waiterLock;
std::once_flag OnceFlag;
std::thread* Shader::WorkerThread = NULL;
HANDLE Shader::WorkerWaiting = NULL;
void Shader::AddCommandToQueue(CommandStruct* cmd, IScriptEnvironment* env) {
// Add command to queue.
cmdBuffer.push(*cmd);
// If thread is idle or not running, make it run.
if (WorkerWaiting != NULL)
SetEvent(WorkerWaiting);
if (WorkerThread == NULL) {
startLock.lock();
if (WorkerThread == NULL)
WorkerThread = new std::thread(StartWorkerThread, env);
startLock.unlock();
}
}
void Shader::StartWorkerThread(IScriptEnvironment* env) {
if (WorkerThread != NULL)
return;
ProcessFrames Worker(env);
// Start waiting event with a state meaning it's not waiting.
WorkerWaiting = CreateEvent(NULL, TRUE, TRUE, NULL);
// Process all commands in the queue.
CommandStruct CurrentCmd, PreviousCmd;
if (!cmdBuffer.try_pop(CurrentCmd))
CurrentCmd.Path = NULL;
while (CurrentCmd.Path != NULL) {
// The result of the 1st execution will be returned on the 2nd call.
if (FAILED(Worker.Execute(&CurrentCmd, NULL)))
env->ThrowError("Shader: Failed to execute command");
initLock.lock();
SetEvent(CurrentCmd.Event); // Notify that processing is completed.
initLock.unlock();
PreviousCmd = CurrentCmd;
if (!cmdBuffer.try_pop(CurrentCmd))
CurrentCmd.Path = NULL;
while (CurrentCmd.Path != NULL) {
if (FAILED(Worker.Execute(&CurrentCmd, NULL)))
env->ThrowError("Shader: Failed to execute command");
initLock.lock();
SetEvent(CurrentCmd.Event); // Notify that processing is completed.
initLock.unlock();
PreviousCmd = CurrentCmd;
if (!cmdBuffer.try_pop(CurrentCmd))
CurrentCmd.Path = NULL;
}
// Flush the device to get last frame.
//Worker.Flush(&PreviousCmd);
//SetEvent(PreviousCmd.WorkerEvent); // Notify that processing is completed.
// When queue is empty, Wait for event to be set by AddCommandToQueue.
waiterLock.lock();
WaitForSingleObject(WorkerWaiting, 10000);
ResetEvent(WorkerWaiting);
// If there are still no commands after timeout, stop thread.
if (!cmdBuffer.try_pop(CurrentCmd))
CurrentCmd.Path = NULL;
waiterLock.unlock();
}
// Release event and thread.
startLock.lock();
CloseHandle(WorkerWaiting);
WorkerWaiting = NULL;
WorkerThread = NULL;
startLock.unlock();
}
编辑:即使我在调用SetEvent(WorkerWaiting)之前清楚地将命令添加到缓冲区,但在线程内,WaitForSingleObject(WorkerWaiting,10000)在缓冲区仍为空时被释放!并且线程反复退出。
这是Github上的完整代码文件 https://github.com/mysteryx93/AviSynthShader/blob/master/Src/WorkerThread.h