none
「InterlockedDecrementやInterlockedIncrement→比較」について RRS feed

  • 質問

  • InterlockedIncrementなどは、戻り値の評価も含んでアトミックであることが保証されるのでしょうか?(そうでないと参照カウント操作について以下の形は不完全な気がするのですが)

    struct AAA {

        ULONG i;

        void AddRef(){
            InterlockedIncrement(&i);
        }

        void Release(){
            if ( InterlockedDecrement(&i) == 0 )    delete this;
        }

    };

    便宜上
    InterLokedDecrement→--
    InterlockedIncrement→++
    と書きます。

    コンストラクタ・iの値とか仮想関数にすべきとか呼び出し規約は__stdcallの方がいいのでは?戻り値は・・・?とか細かいところは本題でないので省略するとして、--(&i) == 0がアトミックでない場合、以下の手順になった場合限定ですがやばい気がします。

    1.スレッド1がRelease関数に入り、--。
    2.--終了後 ==0の比較の前にスレッド2がAddRef関数を呼び出す。
    3.==0との比較が成立し、ここで++(&i)の前にdelete thisまで行われれば++が呼ばれたときにthis->iに対するアクセス違反
    4.++後にdelete this の場合、AddRefした側が今後このクラスを使用しようとするとアクセス違反

    となるような気がするのですが、実際はこうはならないのでしょうか?

    2013年7月6日 6:10

回答

  • Interlocked だけでいえば起きえます。
    ただし、実際にはそういったシビアな状況になることはないと予想されます。

    たとえば、Release で 0 になる状況で、後から、あるいは同時に AddRef することは誰かが何か間違えています。
    間違えている可能性としては、自分の分以外の参照カウントを減らそうとしている、あるいは参照カウントを増やさずに使っているということです。
    (AddRef を実行しようとする人は参照を保持しているはずなので、 1 以上のはずだし、それを Release しようとするのは「AddRef したいのになぜか先に Release する」か、「自分の知らない誰かが勝手に Release する」か、「自分が参照カウント増やし忘れてた」かでしょう)

    2013年7月6日 10:58
    モデレータ
  • スレッド 1 で得るのは 1 であり、スレッド 2 で得るのは 0 であるというのは InterlockedDecrement が保障していることではないでしょうか。
    == 演算子で比較する対象は戻り値(スタック)であり、メンバー変数ではありません。
    (この性質がなければ、そもそも API を使う必要もないので…)
    2013年7月6日 12:42
    モデレータ

すべての返信

  • Interlocked だけでいえば起きえます。
    ただし、実際にはそういったシビアな状況になることはないと予想されます。

    たとえば、Release で 0 になる状況で、後から、あるいは同時に AddRef することは誰かが何か間違えています。
    間違えている可能性としては、自分の分以外の参照カウントを減らそうとしている、あるいは参照カウントを増やさずに使っているということです。
    (AddRef を実行しようとする人は参照を保持しているはずなので、 1 以上のはずだし、それを Release しようとするのは「AddRef したいのになぜか先に Release する」か、「自分の知らない誰かが勝手に Release する」か、「自分が参照カウント増やし忘れてた」かでしょう)

    2013年7月6日 10:58
    モデレータ
  • あ、そうかw
    根本的にその通りですね。(うっかりしていました)

    参照を保持している部分に何らかの方法で問い合わせない限りその参照先を得ることはできない以上、そうせずしてReleaseは不可能、その事実にスレッドの数は無関係で、したがってその問い合わせの時に確実に1回AddRef、参照破棄の時に確実に1回Releaseし、Release済みは2重に使わないという基本を守ってさえいれば、スレッドがいくつあっても無問題、ということですね。

    これは安心しました。ありがとうございます。

    • 編集済み mr.setup 2013年7月6日 11:28 少し付加
    2013年7月6日 11:14
  • ・・・と思ったのですが、それらを守っていても危険そうな手順を思いついてしまいました。

    1.残り参照カウント2で、2つのスレッドが1つずつ保持。
    2.両者がほぼ同時にReleaseを呼び出す。
    3.スレッド1の--終了後 ==0の比較の前にスレッド2の--が成立。
    4.その後どちらも==0が成立するためdelete thisが2回呼び出される

    これは防げるのでしょうか?
    2013年7月6日 12:36
  • スレッド 1 で得るのは 1 であり、スレッド 2 で得るのは 0 であるというのは InterlockedDecrement が保障していることではないでしょうか。
    == 演算子で比較する対象は戻り値(スタック)であり、メンバー変数ではありません。
    (この性質がなければ、そもそも API を使う必要もないので…)
    2013年7月6日 12:42
    モデレータ
  • あ、なるほど、戻り値(内部でのデクリメント操作~取得)までがアトミック操作確定、ということですね。(確かにそうじゃないとこれら関数の存在意義がないですね)
    それぞれのスレッド上でレジスタかスタックかに値が保存された時点で、0と1に確定しているので問題ない、と
    そういうわけですね。
    暑さと疲労で今日は少々頭がやられていたのかもしれませんw

    今度こそ安心して考え直せそうです。ありがとうございました。

    • 編集済み mr.setup 2013年7月6日 12:51 微修正。
    2013年7月6日 12:50