【问题标题】:Is this safe across threads?这跨线程安全吗?
【发布时间】:2010-06-28 07:59:36
【问题描述】:

我有一些看起来像这样的代码:

线程 0:

CMyOtherClass *m_myclass;

CMyClass::SomeFunc(DWORD SomeParam)
{

m_myclass = new CMyOtherClass(SomeParam);

}

线程 1:

CMyClass::InsideSecondThread()
{

   MySecondThreadFunc(*m_myclass);

}

CMyClass::MySecondThreadFunc(MyOtherClass& myclass)  
{

// do something with myclass variable....
// What about thread safety???  Is this not now a shared variable?
// or have we passed a copy and are we safe?
// SomeParam should be the same while in this thread, however it can be changed by Thread 0


}

所以我的问题是,如果你要跨线程传递这个 m_myclass 变量,这是否是线程安全的?

【问题讨论】:

    标签: c++ pointers multithreading thread-safety


    【解决方案1】:

    它不是线程安全的。

    【讨论】:

    • 进一步解释。他的代码中没有对任何变量的冲突访问。
    • 存在冲突。 CMyClass::SomeFunc() 正在创建一个对象 ("m_myclass= new CMyOtherClass();") 并且 CMyClass::MySecondThreadFunc(MyOtherClass& myclass) 正在用它做某事。它不是线程安全的。
    • 我的错,我一如既往地跳过了评论并且未能正确理解问题(“你可以跨线程传递变量”,你当然可以)。
    • @Jurily,这与new 无关。这是非原子的赋值。
    【解决方案2】:

    它不是线程安全的。 如果线程 1 在线程 0 创建对象之前执行,那么您最终会访问线程 1 中的 NULL 指针。(假设 m_myclass 在构造函数中初始化为 NULL)

    还有一点是:

    CMyClass::MySecondThreadFunc(MyOtherClass& myclass) 
    

    myclass 对象是一个共享对象,来自不同线程的任何操作都会产生问题。 除非有共享静态成员,否则本地副本应该在这里为线程安全工作。

    【讨论】:

    • m_myclass 未在 ctor 中初始化。刚刚声明...作为一个指针。
    • 那么最终可能会访问存储在 myclass 中的垃圾值
    【解决方案3】:

    您可以创建一个新的CMyOtherThreadSafeClass,它有两个成员 CMyOtherClass *m_myclassCRITICAL_SECTION cs。如果您需要访问 CMyOtherClass *m_myclass 成员(阅读或修改),您应该在 InitializeCriticalSectionAndSpinCount 构造函数中使用 InitializeCriticalSectionAndSpinCount 或更好的 InitializeCriticalSectionAndSpinCount 函数,并在任何地方使用 EnterCriticalSectionLeaveCriticalSection(请参阅 http://msdn.microsoft.com/en-us/library/aa910712.aspxhttp://msdn.microsoft.com/en-us/library/ms686908.aspx) ,分配或释放)。

    您可以在CMyOtherThreadSafeClass 中包含一个方法,以便更轻松地使用EnterCriticalSectionLeaveCriticalSection。如果您的程序的所有线程都将使用EnterCriticalSectionLeaveCriticalSection,那么您的程序将是线程安全的。

    【讨论】:

    • 呸。如果您想成为特定于平台的,因为 Visual C++ 2008(甚至可能是 2005?)所有volatile 写入都会引入内存屏障,因此在 Windows 上使指针易失就足够了。
    • @Ben Voigt:对不起,我不明白你的评论。当然,volatile 的使用有助于消除缓存并确保所有线程始终从内存中读取并且不做额外的优化假设。但是volatile 使对象不是线程安全的。我没有编写CMyOtherThreadSafeClass 的完整实现,但CRITICAL_SECTION 的使用我认为是必需的。可以使用其他同步对象,但我认为关键部分对象是最好和最快捷的选择。
    • 正如@Ben 刚才所说,在 Visual Studio 上,volatile 也强制执行内存屏障,因此在特定编译器下,在特定情况下 volatile 就足够了。跨度>
    • @jalf & Ben:我看到您将问题从问题解释为使指针线程安全的问题。我不。我很乐意使用 volatile 作为指针,但我怀疑这件事能否解决问题,托尼在他的真实代码中就有这个问题。我不清楚来自m_myclass 的数据将在何处被释放以及何时何地进行线程安全初始化。所以我强烈建议使用临界区。如果它们不是真正需要的,那么将花费很少的资源开销。
    【解决方案4】:

    如果您仅在创建第二个线程之前调用CMyClass::SomeFunc,则不存在竞争条件。

    否则,阻止双重检查锁定工作的问题基本上是相同的:可以在实际构造对象之前将指针分配给m_myclass,然后线程使用未构造(或部分构造)的对象。

    【讨论】:

      【解决方案5】:

      这不是线程安全的,因为两个线程都在访问同一位内存。

      就您的代码而言,只要线程 0 在线程 1 启动之前分配内存,那么您应该没有问题,因为线程 0 除了分配之外什么都不做。如果您更改线程 0 代码以便它开始修改类实例,那么您将开始遇到问题。

      [编辑] 删除了我认为应该是线程安全的示例。 SO上有另一篇帖子询问whether changing a pointer is considered an atomic action,值得一读。

      【讨论】:

      • 线程0实际上修改了对象构造的两个参数
      • 好吧,只要在线程 1 启动之前完成,您仍然是安全的。为了将来参考,如果您想要最准确的答案,最好发布一个尽可能完整的示例。
      • @Jon Cage,您发布的程序非常错误,因为它包含数据竞争。您需要同步对变量的访问。
      • 我一直认为指针是线程安全的,但从听起来我到目前为止还算幸运——哎呀!感谢您的更正avakar。
      • 指针操作是线程安全的,但它们不一定与您对目标所做的操作同步,除非您使用内存屏障。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-13
      • 1970-01-01
      相关资源
      最近更新 更多