【问题标题】:Is it safe to pass (synchronously) stack-allocated memory to other thread?将(同步)堆栈分配的内存传递给其他线程是否安全?
【发布时间】:2011-01-15 13:18:36
【问题描述】:

最近听说栈内存不与其他线程共享,堆内存与其他线程共享。

我通常这样做:

HWND otherThreadHwnd;
DWORD commandId;
// initialize commandId and otherThreadHwnd

struct MyData {
  int data1_;
  long data2_;
  void* chunk_;
};

int abc() {
  MyData myData;
  // initialize myData
  SendMessage(otherThreadHwnd,commandId,&myData);
  // read myData
}

这样可以吗?

【问题讨论】:

  • 内存没问题。然而,死锁是可能的。如果调用线程本身泵送消息循环,则会出现令人讨厌的重入问题。考虑 SendMessageTimeout()。

标签: c++ windows-mobile memory-management dynamic-memory-allocation


【解决方案1】:

是的,在这种情况下是安全的。

堆栈上的数据仅在函数调用的生命周期内存在。由于 SendMessage 是一个同步的阻塞调用,因此数据将在该调用期间有效。

如果您将 SendMessage 替换为对 PostMessage、SendNotifyMessage 或 SendMessageCallback 的调用,则此代码将被破坏,因为它们不会阻塞,并且该函数可能在目标窗口收到消息之前已返回。

【讨论】:

【解决方案2】:

是的,没关系。

SendMessage 正在阻塞模式下工作。即使myData被分配到栈中,它的地址仍然对进程中的所有线程可见。每个线程都有自己的私有栈;但是堆栈中的数据可以显式共享,例如,由您的代码共享。但是,如您所料,在这种情况下不要使用PostThreadMessage

【讨论】:

    【解决方案3】:

    我认为你“听说堆栈中的内存不与其他线程共享”的人混淆了 2 个不同的问题:

    1. 对象生命周期 - 堆栈上的数据仅在线程不离开变量名称范围时才有效。在您给出的示例中,您通过同步调用另一个线程来处理此问题。

    2. 内存地址可见性 - 进程的地址 pspace 在该进程中的各个线程之间共享。因此,一个线程可寻址的变量可由该进程中的其他线程寻址。如果您将地址传递给不同进程中的线程,情况就完全不同了,您需要使用一些其他机制(这可能是为了确保内存块映射到两个进程中 - 但我不这样做'不认为这通常可以用堆栈内存来完成)。

    【讨论】:

    • 我需要对此进行更多说明。 [1] 你的意思是一个进程的堆栈和堆内存在其他进程中都不可见,我需要使用特定的功能(例如,用于创建 Windows 的共享内存)来创建对其他进程可见的内存。 [2] 堆栈和堆内存对其他线程都是可见的?
    • @afriza:你的两个问题的答案都是肯定的。一些狡猾的话:严格来说,这两个项目都是特定于平台的,但对于现代 32 位桌面操作系统(或更好)的进程模型,如 Linux 或 Windows,答案是“是”。
    【解决方案4】:

    您听说的是“潜在侵犯隐私”,即与另一个线程共享一个线程私有堆栈上的数据。

    虽然不鼓励这样做,但这只是一个“潜在的”问题——只要同步正确,就可以安全地完成。在您的情况下,此同步是由 ::SendMessage(); 完成的。直到消息在另一个线程中处理后才会返回,因此数据不会超出主线程堆栈的范围。但请注意,无论您在工作线程中使用此指针做什么,都必须在从消息处理程序返回之前完成(如果您将其存储在某处,请务必制作一份副本)。

    【讨论】:

      【解决方案5】:

      正如其他人已经说过的那样,您如何编写它是很好的,并且通常,只要所有内容都同步,当将指向堆栈上对象的指针传递给另一个线程时,什么都不会立即失败。但是,这样做时我会有点畏缩,因为当异常发生或其中一个线程涉及异步 IO 回调时,看起来线程安全的事情可能会超出预期的顺序。如果在您调用 SendMessage 期间其他线程发生异常,它可能会立即返回 0。如果稍后在另一个线程中处理异常,您可能会遇到访问冲突。另一个潜在的危险是,存储在堆栈中的任何内容都不能从另一个线程中强制处理掉。如果它一直在等待某个回调、对象等,并且用户决定取消或退出应用程序,则工作线程无法确保停滞的线程已清理其堆栈上的任何对象。

      我的观点是:在您所描述的简单场景中,一切都运行良好,没有任何变化,并且没有外部依赖项失败,共享指向本地堆栈的指针是安全的 - 但由于在堆上分配实际上就像很简单,它让你有机会在情有可原的情况下从任何线程显式控制对象的生命周期,为什么不直接使用堆呢?

      最后,我强烈建议您对 MyData 结构的 void* chunk_ 成员非常小心,因为如果它被复制到另一个线程中,则它不是线程安全的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-10-09
        • 2011-04-17
        • 2018-01-22
        • 2015-05-01
        • 2017-02-16
        • 1970-01-01
        • 2014-01-02
        相关资源
        最近更新 更多