2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

何もない部分で不具合

Last updated at Posted at 2019-12-30

マルチスレッド処理で使う、参照カウント式のオブジェクト管理の管理プログラムの例


# include <atomic>

class RefObj
{
    std::atomic<int> count;

    // ...

    void Unref_ng();
    void Unref_ok();
    void destruct();

    // ...
};

void RefObj::Unref_ok()
{
    if (0 == --count)
        destruct();
}

void RefObj::Unref_ng()
{
    --count;
    if (0 == count)
        destruct();
}

RefObj::Unref_ng() が駄目な理由は


// Thread A と B でほぼ同時に呼ばれる
void RefObj::Unref_ng()
{
    // Thread A では 2 から 1 に減る
    // Thread B では 1 から 0 に減る
    --count;

    // Thread A からすると「※何もない部分」で Thread B が更新した
    // 結果、Thread A も B も同時に count=0 に見える可能性がある

    if (0 == count)
    {
        // Thread A と B が同時に count=0 だと
        // 同じ処理が、別々のスレッドで実行される

        destruct();

        // メモリ内容の破壊 = 変数が意図しないところで変更される
        // 恐れあり -> 意図しない動作やクラッシュが他所で発生
    }
}

が発生しうるから。


アセンブラ レベルでの動作

g++ の "-S -O3" オプションで出力されたアセンブラ(不要な情報を排除して、シンボルを修正)を見ると


RefObj::Unref_ok():
        pushq   %rbp
        movq    %rsp, %rbp
        lock
        decl    (%rdi)        ;# count をデクリメント
        jne     LBB1_1        ;# デクリメント結果で分岐
        popq    %rbp
        jmp     RefObj::destruct()
LBB1_1:
        popq    %rbp
        retq

RefObj::Unref_ng():
        pushq   %rbp
        movq    %rsp, %rbp
        lock          ;# 直後の decl のみメモリアクセスを排他
        decl    (%rdi)        ;# count をデクリメントして "2" から "1" になっていても
        movl    (%rdi), %eax  ;# count を参照した時点では "0" になっているかも
        testl   %eax, %eax    ;# count 内容で判断して
        je      LBB0_2        ;# 分岐
        popq    %rbp
        retq
LBB0_2:
        popq    %rbp
        jmp     RefObj::destruct()

となってます。問題点は

        lock          ;# 直後の decl のみメモリアクセスを排他
        decl    (%rdi)        ;# count をデクリメントして "2" から "1" になっていても
                              ;# ここで、別スレッドにて count を変更することがある(※何もない部分)
                              ;# メモリアクセスの排他によって、Thread A と B は動作タイミングが変わる
        movl    (%rdi), %eax  ;# count を参照した時点では "0" になっているかも

で、ここでも「何もない部分」が問題になります。

8/16bit 時代のアセンブラ プログラマ間では「割り込み先で書き換わってない?」の一言で理解してもらえるのだが...


この手の不具合が厄介なのは、不具合の発生が CPU の動作タイミングに依存することで、デバッガ上では発生しない状況になりうることです。そうなると、ソース コードと睨めっこして「バグってなさそうでバグってる(概ね正常動作、時々不具合)」部分を探すしかありません。

要は「アテが外れる可能性があるコード」を重点的に探します。


操作は同じなのに、動作が不安定なソフトに遭遇する度に、この手の不具合が考えられる。担当プログラマには動作環境の所為にせず、頑張ってデバッグして欲しい...

2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?