用于C ++对象破坏的C#一次性模式

时间:2017-08-23 22:43:46

标签: c# c++ .net multithreading disposable

我遇到了microsoft提供的一次性模式的实现:https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx

using System;

class BaseClass : IDisposable
{
   // Flag: Has Dispose already been called?
   bool disposed = false;

   // Public implementation of Dispose pattern callable by consumers.
   public void Dispose()
   { 
      Dispose(true);
      GC.SuppressFinalize(this);           
   }

   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (disposed)
         return; 

      if (disposing) {
         // Free any other managed objects here.
         //
      }

      // Free any unmanaged objects here.
      //
      disposed = true;
   }

   ~BaseClass()
   {
      Dispose(false);
   }
}

假设我有一个与此C#类关联的C ++类,我希望delete C ++对象处理C#类以确保我的非托管资源正确释放。我添加了一个函数DestructNative(self),它基本上在关联的C ++对象上进行本机C ++调用delete (CppObject*)self。所以我的代码看起来像这样:

   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (disposed)
         return; 


      if (disposing) {
         // Free any other managed objects here.
         //
      }

      DestructNative(self);
      disposed = true;
   }

所以我的问题是,知道可以从不同的线程调用C#终结器,我是否需要在C ++对象的析构函数中提供同步,以确保在{{{{{{{ 1}}从C#finalizer调用?

其他问题

微软一次性模式是否破损?似乎Dispose(false)标志是一个简单的变量,如果从不同的线程调用终结器,则该变量不会同步。

1 个答案:

答案 0 :(得分:2)

  

微软一次性模式是否破损?似乎dispos flag是一个简单的变量,如果从不同的线程调用终结器,它就不会同步。

不,它没有被打破。

这个问题提出了两个有趣的问题。对于一个预先考虑C++11思考并且不知道线程的类,以下内容会产生什么影响。

 class PreCpp11 {
   public:
     int ** ptr;
     bool   mInitDone;
     PreCpp11() : mInitDone(false) {
         ptr = new int*[100];
     }
     init() {
         for( size_t i = 0; i < 100; i++ ){
             ptr[i] = new int[100];
         }
         mInitDone = true;
     }
     ~PreCpp11() {
        if( mInitDone ){
            for( size_t i =0; i <100; i++ ){
                delete ptr[i];
            }
        }
        delete []ptr;
     }
 }

代码

之后
PreCpp11 * myObj = new PreCpp11();
myObj->init();
send_object_to_thread2( myObj );

线程2执行的地方

 PreCpp11 obj = get_obj_from_sync();
 delete obj;

如果在不同的线程上调用析构函数,我们如何避免数据竞争。

鉴于对于一次性实施,会导致如上所述的数据竞争。

在这两种情况下,我相信这段代码的答案是可以接受的并且是合规的。但是,它依赖于PreCpp11对象的线程间通信本身是合规的。

我的想法......

我有一大堆数据争用机会,这个线程可以保证看到我写入ptr数组的值,但其他线程无法保证发生inter-thread happens-before关系。

但是,当我将我的类与第二个线程进行线程间通信时,发生同步以确保我的指针在启动线程和“disposing”线程之间正确同步,创建inter-thread happens-before关系,在我调用init之后发生了这种情况,线程2中看到的值是线程1看到的值,当它开始与第二个线程通信时。

因此,如果线程1在给予线程2后继续修改对象,则可能发生数据争用,但假设线程之间的通信是兼容的,则第二个线程会看到第一个行为。

来自cppreference memory_order

<强>测序的-前

 ->init() is sequenced before 
 send_object_to_thread2( myObj );

发生之前

->init() happens before the synchronized communication with thread2.

跨线程发生 - 之前

->init() happens before thread 2 gets the data and calls the destructor
 ->init() is sequenced-before the synchronized write to the inter-thread communication, and the write occurs before the synchronized read.
 The actual write-read is ordered, as they are synchronized.

因此,只要对象的线程间通信是同步的,并且在传递给新线程后不再对对象进行进一步修改,就不会有数据争用。