C#线程同步的方案

需求

对一个数进行循环累加得到结果。

如果数值是0,循环1000000,我们期望的结果是应该等于1000000,但多线程执行累加,如果不进行处理就不能得到我们想要的结果。

处理方案

  • lock 关键字
  • Monitor
  • SpinLock 自旋锁
  • SemaphoreSemaphoreSlim 信号量
  • Interlocked 原子操作

先来看执行结果:
执行结果

可以看到除了第一个,其它方案按预期执行了。

实现代码

  • 第一个输出的代码,输出结果不是我们想要的
public static void Main()
{
    Console.WriteLine("Hello World!\n");

    long count = 1000000;
    long num = 0;
    object obj = new object();
    SpinLock spin = new SpinLock();
    SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
    Stopwatch stopwatch = new Stopwatch();

    //并行累加
    stopwatch.Start();
    num = 0;
    Parallel.For(0, count, i =>
    {
        num++;
    });
    stopwatch.Stop();
    Console.WriteLine($"并行累加\n结果:{ num }, 耗时:{ stopwatch.ElapsedMilliseconds }\n");
}
  • lock 关键字
//lock
lock (obj)
{
    num++;
}
  • Monitor
//Monitor
Monitor.Enter(obj);
try
{
    num++;
}
finally
{
    Monitor.Exit(obj);
}
  • SpinLock 自旋锁
//自旋锁 SpinLock
bool getLock = false;
spin.Enter(ref getLock);
try
{
    num++;
}
finally
{
    if (getLock)
        spin.Exit();
}
  • SemaphoreSemaphoreSlim 信号量
//使用信号量 SemaphoreSlim
semaphoreSlim.Wait();
num++;
semaphoreSlim.Release();
  • Interlocked 原子操作
//使用原子操作
Interlocked.Increment(ref num);
  • 完整代码
using System.Diagnostics;

namespace boby.Synchronization
{
    internal class Program
    {
        public static void Main()
        {
            Console.WriteLine("Hello World!\n");

            long count = 1000000;
            long num = 0;
            object obj = new object();
            SpinLock spin = new SpinLock();
            SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1);
            Stopwatch stopwatch = new Stopwatch();

            //并行累加
            stopwatch.Start();
            num = 0;
            Parallel.For(0, count, i =>
            {
                num++;
            });
            stopwatch.Stop();
            Console.WriteLine($"并行累加\n结果:{ num }, 耗时:{ stopwatch.ElapsedMilliseconds }\n");

            //lock
            stopwatch.Start();
            num = 0;
            Parallel.For(0, count, i =>
            {
                lock (obj)
                {
                    num++;
                }
            });
            stopwatch.Stop();
            Console.WriteLine($"lock\n结果:{ num }, 耗时:{ stopwatch.ElapsedMilliseconds }\n");

            //Monitor
            stopwatch.Start();
            num = 0;
            Parallel.For(0, count, i =>
            {
                Monitor.Enter(obj);
                try
                {
                    num++;
                }
                finally
                {
                    Monitor.Exit(obj);
                }
            });
            stopwatch.Stop();
            Console.WriteLine($"Monitor\n结果:{ num }, 耗时:{ stopwatch.ElapsedMilliseconds }\n");

            //自旋锁 SpinLock
            stopwatch.Start();
            num = 0;
            Parallel.For(0, count, i =>
            {
                bool getLock = false;
                spin.Enter(ref getLock);
                try
                {
                    num++;
                }
                finally
                {
                    if (getLock)
                        spin.Exit();
                }
            });
            stopwatch.Stop();
            Console.WriteLine($"自旋锁\n结果:{ num }, 耗时:{ stopwatch.ElapsedMilliseconds }\n");

            //使用信号量 SemaphoreSlim
            stopwatch.Start();
            num = 0;
            Parallel.For(0, count, i =>
            {
                semaphoreSlim.Wait();
                num++;
                semaphoreSlim.Release();
            });
            stopwatch.Stop();
            Console.WriteLine($"信号量\n结果:{ num }, 耗时:{ stopwatch.ElapsedMilliseconds }\n");

            //使用原子操作
            stopwatch.Start();
            num = 0;
            Parallel.For(0, count, i =>
            {
                Interlocked.Increment(ref num);
            });
            stopwatch.Stop();
            Console.WriteLine($"原子操作\n结果:{ num }, 耗时:{ stopwatch.ElapsedMilliseconds }\n");

            ////使用内存屏障
            //stopwatch.Start();
            //num = 0;
            //Parallel.For(0, count, i =>
            //{
            //    Interlocked.MemoryBarrier();
            //    num++;
            //    Interlocked.MemoryBarrier();
            //});
            //stopwatch.Stop();
            //Console.WriteLine($"结果:{ num }, 内存屏障耗时:{ stopwatch.ElapsedMilliseconds }");
        }
    }
}

总结

  1. lock 关键字本质是语法糖,它等价于下面的代码:

    Monitor.Enter(obj);
    try
    {
        //同步的操作
    }
    finally
    {
        Monitor.Exit(obj);
    }
    

    当然 Monitor 类还支持其它功能,如

posted @ 2022-09-22 18:30  boby,peng  阅读(88)  评论(0)    收藏  举报