103
47

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 3 years have passed since last update.

C#のインクリメント演算子(++)がスレッドセーフじゃないから色々調べた話。

Last updated at Posted at 2020-10-31

17年プログラマをやっていて、いまさら i++ がスレッドセーフじゃないことを知った 1 ことを反省しての記事です。

ざっくりいうと

  • C#のインクリメント演算子はスレッドセーフではありません。
  • スレッドセーフを求める場合は、System.Threading.Interlocked.Incrementを利用しましょう。
  • System.Threading.Interlocked.Incrementがスレッドセーフたる理由を調べてみた。

本文

検証してみる

以下のMSTestを実行してみたところ、テストはエラーになりました。

        [TestMethod]
        public void TestIncrementersThreadSafe()
        {
            var tasks = new List<Task>();
            int counter = 0;
            for (int i = 0; i < 250; i++)
            {
                tasks.Add(Task.Run(() => counter++));
            }
            Task.WaitAll(tasks.ToArray());
            Assert.AreEqual(250, counter);
        }

結果
Assert.AreEqual に失敗しました。<250> が必要ですが、<247> が指定されました。

次のように修正したところ、テストは成功しました。

        [TestMethod]
        public void TestIncrementersThreadSafe()
        {
            var tasks = new List<Task>();
            int counter = 0;
            for (int i = 0; i < 250; i++)
            {
                tasks.Add(Task.Run(() => Interlocked.Increment(ref counter)));
            }
            Task.WaitAll(tasks.ToArray());
            Assert.AreEqual(250, counter);
        }

どういう仕組みなの?

この時点で、なにかしらLockを使ってるんだろうなぁと考えたのですが、どのポインタに対してLockを取っているのかが気になりました。それを知っておかないと、安全には使えませんよね。

というわけで、ソースを覗いてみました。
Microsoft Reference Source - Interlocked.cs

    public static class Interlocked
    {
        //...(中略)...
        [ResourceExposure(ResourceScope.None)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        public static int Increment(ref int location)
        {
            return Add(ref location, 1);
        }
        //...(中略)...
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        public static int Add(ref int location1, int value) 
        {
            return ExchangeAdd(ref location1, value) + value;
        }
        //...(中略)...
        [ResourceExposure(ResourceScope.None)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        internal static extern int ExchangeAdd(ref int location1, int value);

ソースを見ても、Lockをかけているような実装は見当たりません。
どうやらカギは、外部メソッド ExchangeAdd にあるようです。

でも、うーん、それ以上のヒントは見当たりません。

.NETのドキュメントを読んでも、次のように書かれているだけ。
Microsoft Docs - Interlocked Class
The Add method atomically adds an integer value to an integer variable and returns the new value of the variable.
↓Google翻訳
Addメソッドは、整数値を整数変数にアトミックに追加し、変数の新しい値を返します。

「アトミックに」できている理由を知りたいんだよこっちは。。。

ヒントはstackoverflowで見つけた

こんなやり取りを見つけました。
How does Interlocked work and why is it faster than lock? [duplicate]

Reed Copseyさんの回答によると:
Interlocked has support at the CPU level, which can do the atomic operation directly.
↓Google翻訳 注:カッコ内は私の意訳
InterlockedはCPUレベルでサポートされており、(これを利用することで)アトミック操作を直接実行できます。

CPUレベルで!?

続けて、こうおっしゃっています。
For example, Interlocked.Increment is effectively an XADD
↓Google翻訳
たとえば、Interlocked.Incrementは事実上XADDです

なるほど、XADDね!(・∀・)

いや、なんですか、XADDて。。。(;´・ω・)

XADDを調べた

探すのが難しかったのですが、私の結論は以下の記事で行きつきました。

atomic fetch-and-add vs compare-and-swap - An Oracle blog about Transactional locks - Dave's Blog

The x86 architecture exposes LOCK:XADD which I'll use in the discussion below.
↓Google翻訳
x86アーキテクチャは、以下の説明で使用するLOCK:XADDを公開します。

そしてWikipediaの以下のあたりの記述。
Fetch-and-add - wikipedia.org

つまり、x86アーキテクチャは、加算をアトミックに(つまり一連の操作として)行うことをサポートしていると。

なるほど、Interlocked.Incrementはこれを利用しているから、加算している間に他スレッドの割り込みを受けることがなく、スレッドセーフになっていると。納得!

性能ってどうなの?

まとめてくださっている記事がありました。
マルチスレッドで高速なC#を書くためのロック戦略 - @tadokoro

スレッドセーフの中では、Interlocked.Incrementが最速ですね。さすがCPUレベル。

最後に

私の中のエンジニアが「それを知っておかないと、安全には使えませんよね。」とか言い出したために、思ってたよりもめんどくさ大変な調査になりましたが、いろいろ有意義な情報にたどり着くことが出来ました。

但し、CPUレベルな部分について殆ど知識がない中、公開されている情報や回答をつなぎ合わせて推論し出した結論ですので、理解が間違っている部分があるかもしれません。その場合はご教授いただけると有り難いです。

ところで x64アーキテクチャの場合はどうなの?

うん、力尽きた。( ;∀;)
どなたかご教授いただけましたら、ありがたいです。。。(情報のソースもあると嬉しいです)

  1. 今までそんな実装の需要がなかったんだもの。。。

103
47
3

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
103
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?