PostMessage 是不是可以在 GetMessage 之后使工作线程中的变量更改在 UI 线程中可见?
Posted
技术标签:
【中文标题】PostMessage 是不是可以在 GetMessage 之后使工作线程中的变量更改在 UI 线程中可见?【英文标题】:Can PostMessage make variables' changes in a worker thread visible in the UI thread after GetMessage?PostMessage 是否可以在 GetMessage 之后使工作线程中的变量更改在 UI 线程中可见? 【发布时间】:2013-12-22 05:03:25 【问题描述】:我的问题实际上是,如果我在另一个线程中写入一个变量,然后将PostMessage
写入 Wnd,UI 线程中的GetMessage
会与它同步并且我可以安全地读取该变量吗?
背景是:我想使用 PostMessage
从后台线程更新 UI,并且担心数据竞争。我需要其他同步实用程序吗?
谢谢。
编辑: 标题很混乱,所以改一下吧。
详细说明这个案例:
假设我想更新一个std::string
,它是一个全局变量。由于我在PostMessage
之前更新了string
,我可以安全地读取处理该消息的Window Proc 中的string
吗?
我熟悉 C++11 多线程术语,如 happens-before
、sequence-before
、synchronize-with
和 release-acquire
概念,所以我的问题可以换个方式说:
写string
发生在阅读之前吗?
PS:假设这是一次性的事情,工作线程不会一次又一次地更新string
。
【问题讨论】:
您关心什么竞争条件?由于您正在向队列发布消息,因此消息将自动按照收到的方式排序。 @haohaolee,让我稍微纠正一下。您不必担心数据竞争。在您的情况下,您没有数据竞争。您应该担心内存读取/写入重新排序。 @AlexAntonov 如果我理解正确,您的意思是写入字符串可以在PostMessage
之后重新排序,对吗?但我认为它必须在此之前,因为它在PostMessage
之前排序,所有副作用都应该发生在字符串上
@haohaolee 我想说的第一件事是您的代码中没有数据竞争(竞争条件)。当两个线程同时访问同一个内存区域(并且至少有一个线程正在写入)时,就会出现竞争条件。在您的程序序列中[写入字符串 - PostMessage - 接收消息 - 从另一个线程中的字符串读取] 线程在不同的时间访问字符串(在消息发布之前您无法接收消息)。所以这里不可能出现竞争条件(除非您在 PostMessage 之后写入字符串,例如删除字符串)。
@haohaolee 在您的情况下,我对线程安全的唯一担忧是在PostMessage
和从字符串读取之后,写入要重新排序的字符串的可能性。但是在考虑了您对我的回答的评论后,我同意您的观点,即使用PostMessage
时内存读/写重新排序是不可能的。 PostMessage
对于在线程之间发布结构是完全安全的。例如,SBM_SETSCROLLINFO 消息通过指向 SCROLLINFO 结构的指针发送/发布。在线程之间发布时不需要任何额外的同步工作... 待继续
【参考方案1】:
Windows 消息队列是线程安全的生产者-消费者队列。 MSG 结构(仅此而已)被复制到队列中,因此一旦发布了消息,您就可以重新加载自己的消息数据和 PostMessage 再次没有问题。
当开发人员将 PostMessage 指针/引用作为 wParam、lParam 时,生命周期会出现问题。如果指针指向本地堆栈对象,它可以在指向它的指针在接收 GetMessage/wndProc 中处理之前得到 RAII-ed。同样,如果指向使用 new 实例化的对象的指针在 GetMessage 线程中处理之前在发布线程中被显式删除。
如果您提供有关您在 PostMessaging 中的哪些数据的更多详细信息,我们应该能够为您提供建议。
【讨论】:
【参考方案2】:没有。你对此有零控制。 PostMessage
是真正异步的,在处理消息时,两个线程中已经发生了很多事情(包括处理之前发布的消息,以及发送的消息(通过 SendMessage)。
根据你想做什么,你可以:
-
异步传输数据的副本(
malloc
/new
)和PostMessage
(wParam
或 lParam
中的指针)
用SendMessage
同步传输指向数据的指针
用SendMessage
和WM_COPYDATA同步传输数据的副本
【讨论】:
【参考方案3】:它可以保证 UI 线程将使用数据并可以安全地更新窗口。这就是结束的地方,您仍然有 另一个 线程正在从写入数据的线程读取数据,因此需要正常的互锁以确保 UI 线程不会读取 更改数据。
这是一个很常见的问题,您无法准确地知道何时 UI 线程检索到消息。如果 UI 线程陷入困境,可能需要很长时间。因此,除非您明确地握手,否则您无法知道工作线程何时可以继续更新数据或何时可以发布另一条消息。创建数据的深层副本可以解决这个问题。或者你需要一个自动重置事件来握手,小心它导致死锁的能力。
您必须处理的另一种可能的故障模式是 PostMessage() 可能会失败。当 UI 线程由于任何原因陷入困境时,就会发生这种情况,只要消息队列大小超过配额(默认为 10,000 条消息),您就会得到 FALSE 返回。对此您无能为力,只能睡一会儿再试一次。
当您遇到消防软管问题时,您还将调用此故障模式,工作线程以比 UI 线程能够跟上的速度更快的速度生成数据。 Fire-hosing 特别棘手,因为当您使用有限的数据集开始或调试程序时,它很少发生。诊断是 WM_PAINT 通知丢失,您的 UI 似乎冻结,即使它仍然存在并积极处理已发布消息的积压。您可以通过保持在人眼可以感知的 UI 更新速率的北部来避免这种情况。这既好又慢,当您每秒更新超过 25 次时,这一切都会变成难以理解的模糊。
【讨论】:
嘿 - 我曾多次遭受 WMQ 过载的困扰,通常是在涉及树视图控件时,因此 GUI 更新非常缓慢。【参考方案4】:PostMessage
/GetMessage
的文档不对synchronize-with
关系提供任何保证。因此,即使当前/最新版本的 Windows 在您的硬件平台上实际上为 PostMessage
/GetMessage
提供了 synchronize-with
关系,您也不应该依赖它,因为它可以在较新版本的 Windows 中进行更改,或者它可能无法正常工作在其他硬件平台上。因此,在这种特殊情况下,IMO 您必须自己提供synchronize-with
关系,以使您的多线程代码防弹。例如,您可以使用 InterlockedXxx 函数(它们同时具有获取和释放语义)或 std::atomic 来实现。
【讨论】:
确实,文档中没有任何关于同步关系的内容,但很难想象它们彼此不同步,因为GetMessage
看到了正确的消息,应该是PostMessage
帖子以上是关于PostMessage 是不是可以在 GetMessage 之后使工作线程中的变量更改在 UI 线程中可见?的主要内容,如果未能解决你的问题,请参考以下文章
使用 window.postMessage 发送敏感数据是不是安全?
Internet Explorer 11 中的 window.postMessage 是不是有最大长度限制?