none
デストラクタはスレッドセーフですか? RRS feed

  • 質問

  • 質問1 デストラクタは(コンストラクタと同様に)スレッドセーフが保障されていますか?

    質問2 保障されていない場合、デストラクタに lock(this){ ...  }ステートメントを挿入するのが適切ですか?


    C#開発者
    2010年8月6日 6:07

回答

  • 質問1 デストラクタにlockを挿入する必要は無いという理解で正しいですか?

    デストラクタには lock は入れなくていいと思います。

    質問2 マルチコア環境で使用している.NET 4.0も同様の理解で正しいですか?
    Finalizer (C# のデストラクタ) が定義されていると、一旦キューに入れられた後、GC のスレッドから非同期で呼び出されるわけですが、そこのあたりの仕組みは変わっていないので、.NET Framework 4 でも同じでいいと思います。(そこを複数スレッドから呼び出される可能性が...なんて仕様変更をされたら、既存のコードがすべて使えなくなってしまいます。)

      ガベージ コレクションの基礎
      http://msdn.microsoft.com/ja-jp/library/ee787088.aspx

    (推奨はできませんが)ソースコード中で(誰かが)明示的にFinalize()メソッドを呼び出したらどうなりますか?そのような場合の予防措置としてデストラクタにlockを入れておくことは有益ですか?


    そこまでやる必要はないような...。
    C# で Object.Finalize を呼び出すことはできませんし、Finalize をオーバーライドしようとするとコンパイル エラーが出ます。
    リフレクションとかを駆使すれば無理やり Finalize を呼び出せるのかもしれませんが、そこまで悪質なケースは考えなくてもいいのではないでしょうか。

      アンマネージ リソースをクリーンアップするための Finalize および Dispose の実装
      http://msdn.microsoft.com/ja-jp/library/b1yfkh5e.aspx

    Dispose に関しては、自分で lock してスレッド セーフにしないといけないですが。

      Dispose メソッドの実装
      http://msdn.microsoft.com/ja-jp/library/fs2xkftw.aspx

    • 回答としてマーク MicroVAX 2010年9月28日 10:31
    2010年8月7日 8:16
  • 個人的にはDisposeがスレッドセーフで無い状況で呼ばれること自体がダメで、何かあるとすればそもそも使用側の問題では?と思ったりもします。
    ただ、だからといって何もしないのも乱暴なので手を打つのは良いと思います。

    で、ファイナライザ(デストラクタ)の法も心配なのであれば下のようにDispose(bool disposing)にlockを入れれば、よい気もしますがどうでしょう?

    public void Dispose()
    {
        Dispose(true);
        GC.SupressFinalize();
    }

    private void Dispose(bool disposing)
    {
        lock(this)
        {
              // ~省略~
        }


    ~Class1()
    {
        Dispose(false);
    }

    • 回答としてマーク MicroVAX 2010年9月28日 10:31
    2010年8月10日 1:09

すべての返信

  • デストラクターは複数のスレッドから同時に呼び出されることはないはずなので...。
    文法的にはもちろん可能なのでしょうが、1つのインスタンスを複数個所から破棄するのなら、そもそも大きなバグなのでは?
    2010年8月6日 6:19
  • totojoさんの回答に補足です。

    たしかデストラクタを明示的に呼び出す構文がないため、.NETラインタイムからしか呼び出されません。そして.NETランタイムはガーベージコレクタの管理下、リソース解放される段階で1回しか呼び出さないことになっています。

    というわけでスレッドセーフかどうかは関係ありません。

    なお、デストラクタはメインスレッドとは別にガーベージコレクタスレッドから呼び出される可能性もあるため、.NETとしてはデストラクタではなく、IDisposableの使用が推奨されています。こちらはメインスレッドから明示的に呼び出すことが可能です。

    2010年8月6日 7:37
  •  .NET 4.0ではGCの動作が一部変更になったと聞いています。.NET 4.0に移行する準備としてDispose()パターンを見直してセキュアーな実装方法を確立しておこうと思い質問しました。よろしくお願いします。

    確認事項
    質問1 デストラクタにlockを挿入する必要は無いという理解で正しいですか?

    質問2 マルチコア環境で使用している.NET 4.0も同様の理解で正しいですか?

    質問3 (推奨はできませんが)ソースコード中で(誰かが)明示的にFinalize()メソッドを呼び出したらどうなりますか?そのような場合の予防措置としてデストラクタにlockを入れておくことは有益ですか?

    質問4 下記のDispose()パターンについて、その他、改善点などお気づきの点をご指摘いただければ助かります。

    public sealed class Class1 : IDisposable
    {
        public Class1()
        {
            // 何かの処理
        }

        ~Class1()
        {
            Dispose(false);
            // 右記のようなlockは不要 lock(this) { Dispose(false); }
        }
       
        public void Dispose()
        {
             lock(this)
            {
                Dispose(true);
                GC.SupressFinalize();
            }
        }

        private void Dispose(bool disposing)
        {
            if(disposing)
            {
                // マネージリソースの解放
                if(_managed != null)
           {
                      _managed = null;
                }
            }
     // 非マネージリソースの解放
     if(_unmaganed != null)
            {
                _unmanaged.Dispose();
                _unmanaged = null;
            }
        }
       
        // 非マネージリソースのフィールド
        private Unmanaged _unmanaged;
        // マネージリソースのフィールド
        private Managed   _managed;
    }


    C#開発者
    2010年8月7日 4:45
  • 質問1 デストラクタにlockを挿入する必要は無いという理解で正しいですか?

    デストラクタには lock は入れなくていいと思います。

    質問2 マルチコア環境で使用している.NET 4.0も同様の理解で正しいですか?
    Finalizer (C# のデストラクタ) が定義されていると、一旦キューに入れられた後、GC のスレッドから非同期で呼び出されるわけですが、そこのあたりの仕組みは変わっていないので、.NET Framework 4 でも同じでいいと思います。(そこを複数スレッドから呼び出される可能性が...なんて仕様変更をされたら、既存のコードがすべて使えなくなってしまいます。)

      ガベージ コレクションの基礎
      http://msdn.microsoft.com/ja-jp/library/ee787088.aspx

    (推奨はできませんが)ソースコード中で(誰かが)明示的にFinalize()メソッドを呼び出したらどうなりますか?そのような場合の予防措置としてデストラクタにlockを入れておくことは有益ですか?


    そこまでやる必要はないような...。
    C# で Object.Finalize を呼び出すことはできませんし、Finalize をオーバーライドしようとするとコンパイル エラーが出ます。
    リフレクションとかを駆使すれば無理やり Finalize を呼び出せるのかもしれませんが、そこまで悪質なケースは考えなくてもいいのではないでしょうか。

      アンマネージ リソースをクリーンアップするための Finalize および Dispose の実装
      http://msdn.microsoft.com/ja-jp/library/b1yfkh5e.aspx

    Dispose に関しては、自分で lock してスレッド セーフにしないといけないですが。

      Dispose メソッドの実装
      http://msdn.microsoft.com/ja-jp/library/fs2xkftw.aspx

    • 回答としてマーク MicroVAX 2010年9月28日 10:31
    2010年8月7日 8:16
  • 個人的にはDisposeがスレッドセーフで無い状況で呼ばれること自体がダメで、何かあるとすればそもそも使用側の問題では?と思ったりもします。
    ただ、だからといって何もしないのも乱暴なので手を打つのは良いと思います。

    で、ファイナライザ(デストラクタ)の法も心配なのであれば下のようにDispose(bool disposing)にlockを入れれば、よい気もしますがどうでしょう?

    public void Dispose()
    {
        Dispose(true);
        GC.SupressFinalize();
    }

    private void Dispose(bool disposing)
    {
        lock(this)
        {
              // ~省略~
        }


    ~Class1()
    {
        Dispose(false);
    }

    • 回答としてマーク MicroVAX 2010年9月28日 10:31
    2010年8月10日 1:09