C# 线程同步

       要避免同步问题,最好不要在线程之间共享数据。当然,这并不总是可行的。如果需要共享数据,就必须使用同步技术。如果不注意这些问题,就很难在应用程序中找到问题的原因,因为线程问题是不定期发生的。

1. lock 

 C#为多个线程的同步提供了自己的关键字:lock语句。lock 语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。 持有 lock 时,持有 lock 的线程可以再次获取并释放 lock。 阻止任何其他线程获取 lock 并等待释放 lock。

namespace ConsoleApp1
{
    using System;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            object lockObj = new object();
            long num = 0;
            Parallel.For(0, 100000, a =>
              {
                  lock (lockObj)
                  {
                      num += a;
                  }
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}

PS:锁只在线程之间有效,同一线程中并不会产生锁,例如下例,递归并不会锁住

namespace ConsoleApp1
{
    using System;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            object lockObj = new object();
            test(20);
            Console.ReadKey();

            void test(int a)
            {
                lock (lockObj)
                {
                    if (a > 10)
                    {
                        test(--a);
                    }
                }
                Console.WriteLine(a);
            }
        }
    }
}

lock创建单例: 

public class People
{
    static People _instance;
    static readonly object _lockObj = new object();

    public static People Instance
    {
        get
        {
            if (_instance == null)  //防止每次获取都要进行lock判断
            {
                lock (_lockObj)
                {
                    if (_instance == null)  //防止多线程访问导致初始化多次
                    {
                        _instance = new People();
                    }
                }
            }
            return _instance;
        }
    }
}

2. Monitor

 提供同步访问对象的机制。lock 相当于一个简易的 Monitor。

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            object lockObj = new object();
            long num = 0;
            Parallel.For(0, 100000, a =>
              {
                  Monitor.Enter(lockObj);
                  num += a;
                  Monitor.Exit(lockObj);
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}

相对于 lock,Monitor类还提供了:

TryEnter(object obj, TimeSpan timeout)在指定的时间内尝试获取指定对象上的排他锁。
IsEntered(object obj)确定当前线程是否保留指定对象上的锁。
Wait(object obj)释放对象上的锁并阻止当前线程,直到它重新获取该锁。
Pulse(object obj)通知等待队列中的线程锁定对象状态的更改,允许一个等待中的继续

3. Interlocked  

为多个线程共享的变量提供原子操作。  

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            long num = 0;
            Parallel.For(0, 100000, a =>
              {
                  Interlocked.Add(ref num, a);
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}
Decrement(ref int location)以原子操作的形式递减指定变量的值并存储结果。
Increment(ref int location)以原子操作的形式递增指定变量的值并存储结果。
Exchange(ref int location1, int value)以原子操作的形式,将 32 位有符号整数设置为指定的值并返回原始值。

4. Mutex

Mutex 主要用于进程间同步的同步基元。 

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            long num = 0;
            Mutex mut = new Mutex();
            Parallel.For(0, 100000, a =>
              {
                  mut.WaitOne();
                  num += a;
                  mut.ReleaseMutex();
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}
static class Program
{
    [STAThread]
    static void Main()
    {
        bool createdNew;
        Mutex mutex = new Mutex(false, "SingletonWinAppMutex", out createdNew);
        if (createdNew)
        {
            MessageBox.Show("You can only start one instance of the application"); 
            Application.Exit();
            return;
        }
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

5. AutoResetEvent

用一个指示是否将初始状态设置为终止的布尔值初始化 AutoResetEvent 类的新实例。若为 false,表示起始就是非终止状态,即是锁住状态。 

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            long num = 0;
            AutoResetEvent autoResetEvent = new AutoResetEvent(true);
            Parallel.For(0, 100000, a =>
              {
                  autoResetEvent.WaitOne();
                  num += a;
                  autoResetEvent.Set();
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}
WaitOne()阻止当前线程,直到当前 System.Threading.WaitHandle 收到信号。
Set()将事件状态设置为有信号,从而允许一个WaitOne等待线程继续执行。
Reset()将事件状态设置为非终止,从而导致WaitOne线程受阻。但不会导致本线程受阻。

6. ManualResetEvent 

用法类似于 AutoResetEvent。

区别:

  1. 在 Set 方法上,ManualResetEvent 每次 Set 后会允许所有正在 WaitOne 的线程继续执行。
  2. 需要手动调用Reset将状态设置为受阻状态,AutoResetEvent.WaiteOne 相当于 ManualResetEvent.WaiteOne;ManualResetEvent.Reset;

同时还有 ManualResetEvent 的轻量版:ManualResetEventSlim

7. SpinLock

提供一个相互排斥锁基元,在该基元中,尝试获取锁的线程将在重复检查的循环中等待,直至该锁变为可用为止。 注意:SpinLock 是结构体

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            long num = 0;
            SpinLock spinLock = new SpinLock();
            Parallel.For(0, 100000, a =>
              {
                  bool lockTaken = false;
                  spinLock.Enter(ref lockTaken);
                  num += a;
                  if (lockTaken)
                  {
                      spinLock.Exit();
                  }
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}

除了体系结构上的区别之外, SpinLock结构的用法非常类似于 Monitor类。获得锁定使用 Enter()或TryEnter()方法,释放锁定使用Exit()方法。 如果基于对象的锁定对象( Monitor)的系统开销由于垃圾回收而过高,就可以使用 SpinLock结构。如果有大量的锁定(例如,列表中的每个节点都有一个锁定),且锁定的时间总是非常短, SpinLock结构就很有用。应避免使用多个SpinLock结构,也不要调用任何可能阻塞的内容。

8. Semaphore

可以进行进程间的互斥。 

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            long num = 0;
            Semaphore semaphore = new Semaphore(1, 1);
            Parallel.For(0, 100000, a =>
              {
                  semaphore.WaitOne();
                  num += a;
                  semaphore.Release();
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}

信号量分为两种类型:本地信号量和命名系统信号量。 本地信号灯对应用程序而言是本地的,系统信号量在整个操作系统中均可见,适用于进程间同步。 SemaphoreSlim是 Semaphore 不使用 Windows 内核信号量的类的轻型替代项。 与 Semaphore 类不同, SemaphoreSlim 类不支持已命名的系统信号量。 只能将其用作本地信号量。 SemaphoreSlim类是用于在单个应用内进行同步的建议信号量。 

9. SemaphoreSlim 

对可同时访问资源或资源池的线程数加以限制的 Semaphore 的轻量替代。轻型信号灯控制对应用程序的本地资源池的访问。 实例化信号量时,可以指定可同时进入信号量的最大线程数。 还可以指定可同时进入信号量的初始线程数。 这会定义信号量的计数。 

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            long num = 0;
            SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
            Parallel.For(0, 100000, a =>
              {
                  semaphoreSlim.Wait();
                  num += a;
                  semaphoreSlim.Release();
              });
            Console.WriteLine(num);
            Console.ReadKey();
        }
    }
}

10. ThreadStatic

ThreadStaticAttribute 只能应用与静态字段。被标记的字段不会在线程之间共享。 每个执行线程都有单独的字段实例,并分别设置和获取该字段的值。 如果在不同的线程上访问该字段,则该字段将包含不同的值

namespace ConsoleApp1
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        [ThreadStatic]
        static long num = 0;

        static void Main(string[] args)
        {
            Parallel.For(0, 100000, a =>
              {
                  num = a;  //可以保证num在该线程中的值不会因为其他线程而改变
                  if (num != a)
                  {
                      Console.WriteLine($"num:{num} a:{a}");
                  }
              });
            Console.ReadKey();
        }
    }
}

11. MethodImpl(Synchronized

实现方法的同步:该方法一次性只能在一个线程上执行。 静态方法在类型上锁定,而实例方法在实例上锁定。 只有一个线程可在任意实例函数中执行,且只有一个线程可在任意类的静态函数中执行。 

namespace ConsoleApp1
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading.Tasks;

    class Program
    {
        static void Main(string[] args)
        {
            List<DateTime> listDate = new List<DateTime>();
            Parallel.For(0, 100000, a =>
            {
                Add(listDate);
            });
            Console.WriteLine(listDate.Count);
            Console.ReadKey();
        }


        [MethodImpl(MethodImplOptions.Synchronized)]
        public static void Add(List<DateTime> listDate)
        {
            listDate.Add(DateTime.Now);
        }
    }
}

12. Concurrent 

列举几个线程安全的集合类,不用考虑在插入或者删除时导致枚举失败。 

ConcurrentBag<T>

表示对象的线程安全的无序集合。类似于List,但是不能通过索引获取值

ConcurrentDictionary<TKey,TValue>

相当于 Dictionary,可以通过将 TKey设置为索引相当于List来使用

ConcurrentQueue<T>

相当于Queue,表示线程安全的先进先出 (FIFO) 。

ConcurrentStack<T>

相当于Stack,表示线程安全的后进先出 (LIFO) 。
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bridge_go

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值