我遇到了一个问题,我不确定这个问题是否能以我想要的方式解决。我遇到了竞争条件的问题。
我有一个项目作为C++dll(主引擎(运行。然后我有第二个C#进程,它使用C++/CLI与主引擎(编辑器(通信。
编辑器将引擎窗口作为子窗口托管。这样做的结果是子窗口异步接收输入消息(参见RiProcessMouseMessage()
(。通常只有当我调用window->PollEvents();
时才会发生这种情况。
main engine loop {
RiProcessMouseMessage(); // <- Called by the default windows message poll function from the child window
foreach(inputDevice)
inputDevice->UpdateState();
otherCode->UseCurrentInput();
}
编辑器的主循环是我不控制的WPF循环。基本上它是这样做的:
main editor loop {
RiProcessMouseMessage(); // <- This one is called by the editor (parent) window, but is using the message loop of the (child) engine window
}
RawInput处理器,被引擎称为sync,被编辑器称为async:
void Win32RawInput::RiProcessMouseMessage(const RAWMOUSE& rmouse, HWND hWnd) {
MouseState& state = Input::mouse._GetGatherState();
// Check Mouse Position Relative Motion
if (rmouse.usFlags == MOUSE_MOVE_RELATIVE) {
vec2f delta((float)rmouse.lLastX, (float)rmouse.lLastY);
delta *= MOUSE_SCALE;
state.movement += delta;
POINT p;
GetCursorPos(&p);
state.cursorPosGlobal = vec2i(p.x, p.y);
ScreenToClient(hWnd, &p);
state.cursorPos = vec2i(p.x, p.y);
}
// Check Mouse Wheel Relative Motion
if (rmouse.usButtonFlags & RI_MOUSE_WHEEL)
state.scrollMovement.y += ((float)(short)rmouse.usButtonData) / WHEEL_DELTA;
if (rmouse.usButtonFlags & RI_MOUSE_HWHEEL)
state.scrollMovement.x += ((float)(short)rmouse.usButtonData) / WHEEL_DELTA;
// Store Mouse Button States
for (int i = 0; i < 5; i++) {
if (rmouse.usButtonFlags & maskDown_[i]) {
state.mouseButtonState[i].pressed = true;
state.mouseButtonState[i].changedThisFrame = true;
} else if (rmouse.usButtonFlags & maskUp_[i]) {
state.mouseButtonState[i].pressed = false;
state.mouseButtonState[i].changedThisFrame = true;
}
}
}
UpdateState()
仅由引擎调用。它基本上将RawInput转换为当前使用的输入。这是为了防止在帧循环的中间(即在otherCode->UseCurrentInput();
期间(的输入更新
void UpdateState() {
currentState = gatherState; // Copy gather state to current
Reset(gatherState); // Reset the old buffer so the next time the buffer it's used it's all good
// Use current state to check stuff
// For the rest of this frame currentState should be used
}
MouseState& _GetGatherState() { return gatherState; }
void Reset(MouseState& state) { // Might need a lock around gatherState :(
state.movement = vec2f::zero;
state.scrollMovement = vec2f::zero;
for (int i = 0; i < 5; ++i)
state.mouseButtonState[i].changedThisFrame = false;
}
因此,正如您所看到的,当在主引擎循环中调用RiProcessMouseMessage()
而调用Reset()
时,竞争条件就会发生。如果不清楚:Reset()
函数需要将状态重置回其帧的默认数据,以便每帧都能正确读取数据。
现在我非常清楚,通过在gatherState更新周围添加mutex
,我可以很容易地解决这个问题,但如果可能的话,我希望避免这种情况。基本上,我想问的是,是否有可能重新设计此代码以实现无锁定?
您要求释放锁,如果两端都改变了缓冲区,这是不可能的。但若你们问锁是优化的,几乎是即时的,那个么你们可以使用FIFO逻辑。您可以使用.net的ConcurrentQueue"https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1?view=net-5.0〃;从该队列写入更新和轮询更新。
如果你真的摆脱了锁,那么你可以检查无锁的圆形数组,也就是无锁的环形缓冲区,如果你想更深入地了解硬件级别,了解其背后的逻辑,那么你可以检查https://electronics.stackexchange.com/questions/317415/how-to-allow-thread-and-interrupt-safe-writing-of-incoming-usart-data-on-freerto因此,您也将了解底层的并发性;在有限制的情况下,当一端只在已知间隔/边界内写入,而另一端只读取时,无锁环形缓冲区可以工作。可以检查类似的问题:环形无锁缓冲
Boost有众所周知的无锁实现:https://www.boost.org/doc/libs/1_65_1/doc/html/lockfree.html