C# 幾種不同 thread-safe counter 的作法效能比較

  • 4404
  • 0

看到專案裡面之前用 SemaphoreSlim 來計數,覺得還要 dispose 有點麻煩,就 google 看看其他人的做法,並寫來比較看看效能

這裡比較了三種做法的時間,用 Unit test 跑的時間來參考,三種分別如下

  1. 內建的 Interlocked.Increment、Interlocked.Decrement
  2. 將 ++ 與 -- 用 lock 陳述式包起來
  3. 用 SemaphoreSlim.Release 與 Semaphore.Wait

 第一種、 Interlocked.Increment、Interlocked.Decrement

        [TestMethod]
        public void InterlockedTest()
        {
            var count = 0;
            var taskList = new List<Task>();
            for (int i = 0; i < 4; i++)
            {
                taskList.Add(Task.Run(() =>
                {
                    for (int j = 0; j < 1_000_000; j++)
                    {
                        Interlocked.Increment(ref count);
                        Interlocked.Decrement(ref count);
                    }
                }));
            }
            Task.WaitAll(taskList.ToArray());
            Assert.AreEqual(0, count);
        }

 

 第二種、將 ++ 與 -- 用 lock 陳述式包起來

        [TestMethod]
        public void LockTest()
        {
            var myLock = new object();

            var count = 0;
            var taskList = new List<Task>();
            for (int i = 0; i < 4; i++)
            {
                taskList.Add(Task.Run(() =>
                {
                    for (int j = 0; j < 1_000_000; j++)
                    {
                        lock (myLock)
                        {
                            count++;
                        }
                        lock (myLock)
                        {
                            count--;
                        }
                    }
                }));
            }
            Task.WaitAll(taskList.ToArray());
            Assert.AreEqual(0, count);
        }

 

 第三種、用 SemaphoreSlim.Release 與 Semaphore.Wait

        [TestMethod]
        public void SemaphoreSlimTest()
        {
            var count = new SemaphoreSlim(0, int.MaxValue);
            var taskList = new List<Task>();
            for (int i = 0; i < 4; i++)
            {
                taskList.Add(Task.Run(() =>
                {
                    for (int j = 0; j < 1_000_000; j++)
                    {
                        count.Release();
                        count.Wait();
                    }
                }));
            }
            Task.WaitAll(taskList.ToArray());
            Assert.AreEqual(0, count.CurrentCount);
        }

 

從測試結果看起來,Interlocked 是最快的,但不同的方法有不同的好處,Interlocked 的效能是最高的,如果有需求頻繁的加加減減,可以用 Interlocked。Lock 的好處是可以連其他希望 Atomic 一起寫進去。SemaphoreSlim 則是做其他事情比較好用,因為有支援 async Wait,SemaphoreSlim 本身不太適合拿來當 Counter,因為只能一定要先加再減,用處比較受限。