看到專案裡面之前用 SemaphoreSlim 來計數,覺得還要 dispose 有點麻煩,就 google 看看其他人的做法,並寫來比較看看效能
這裡比較了三種做法的時間,用 Unit test 跑的時間來參考,三種分別如下
- 內建的 Interlocked.Increment、Interlocked.Decrement
- 將 ++ 與 -- 用 lock 陳述式包起來
- 用 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,因為只能一定要先加再減,用處比較受限。