c# - 改进多线程代码设计以防止竞争条件
问题描述
我遇到了一个问题,我不确定是否可以通过我想要解决的方式来解决。我对比赛条件有疑问。
我有一个项目作为 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 处理器被引擎称为同步,被编辑器称为异步:
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 交换为当前使用的输入。这是为了防止在帧循环中间(又名 during 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()
调用 while时,会发生竞争条件。Reset()
如果不清楚:该Reset()
函数需要将状态重置回其帧默认数据,以便每帧正确读取数据。
现在我非常清楚我可以通过mutex
在gatherState 更新周围添加一个来轻松解决这个问题,但如果可能的话我想避免这种情况。基本上我在问是否可以重新设计此代码以无锁?
解决方案
如果两端都更改缓冲区,您正在询问无锁,这是不太可能的。但是,如果您要求锁定已优化且几乎是即时的,那么您可以使用 FIFO 逻辑。您可以使用 .net 的 ConcurrentQueue "https://docs.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
推荐阅读
- scala - Scala with play:尝试通过 websocket 发送数据时出现 Class Cast Exception
- azure - 天蓝色的功能级授权授权密钥它正在使用哪个机制?
- javascript - 如何确保 Chrome 上 HTML 视频的自动播放脚本能够正常工作?
- angular - Angular 7 fullpage.js 和 AOS
- node.js - 如何在 NodeJS 中正确编写路由器模块的单元测试
- class - C++11:如何在行为类似于子类的类中创建枚举类?
- mysql - MySQL - 获取某个季度和年份之前的时间数据
- coq - Coq 中的部分机制。禁止从上下文中省略假设
- nginx - 如何为不同的上下文 URI 配置 Location 指令
- oop - Tcl oo::class 中的 proc