zoukankan      html  css  js  c++  java
  • C#中的几种锁:用户模式锁、内核模式锁、动态计数、监视锁

    参考网址: https://blog.csdn.net/weixin_43989331/article/details/105356008

    C#中的几种锁:用户模式锁、内核模式锁、动态计数、监视锁

    介绍几种C#中的锁,最常用的是最后的监视锁,其他的也有必要了解一下原理及应用,特别像WaitOneWaitHandle在我们项目中应用还是挺多的。

    文章目录

    C#中的几种锁:用户模式锁、内核模式锁、动态计数、监视锁

    用户模式锁

    内核模式锁

    动态计数锁

    监视锁

    锁:解决多线程中的数据共享安全问题。

    用户模式锁

    volatile关键字:取消release对底层的优化,在读写的时候都从内存中读取

    SpinLock 旋转锁:

    SpinLock spinLock = new SpinLock();

    bool lockTaken = false;

    spinLock.Enter(ref lockTaken);

    spinLock.Exit();

    内核模式锁

    分为:事件锁、信号量、互斥锁、读写锁。

    建议:通常不建议随便使用内核模式锁,资源付出相对较大。我们可以使用混合锁代替,以及我们马上讲到的lock关键字。

    事件锁(自动事件锁、手动事件锁):

    自动事件锁:AutoResetEvent

    AutoResetEvent myLock = new AutoResetEvent(true);//true:表示终止状态(初始状态),false表示非终止

    myLock.WaitOne();

    //...

    myLock.Set();

    手动事件锁:ManualResetEvent,和自动事件锁相比,差距在于可以对多个变量进行批量锁

    ManualResetEvent myLock = new ManualResetEvent(false);//true:可以正常通过的。false:拦截状态,禁止通过。

    myLock.WaitOne();//批量拦截

    //...//由于是一批,这里是无序的

    myLock.Set();

    Semaphore 信号量

    基本原理:是通过int数值来控制线程的个数

    Semaphore myLock = new Semaphore(5, 10);//第一个参数表示同时可以允许的线程数,第二个是最大值

    Semaphore myLock = new Semaphore(1, 10);//每次只能一个线程通过

    Semaphore myLock = new Semaphore(1, 10);

    myLock.WaitOne();

    //...

    myLock.Release();

    Mutex互斥锁

    可用于非全局变量互斥的情况,如同一ID的用户只允许提交一次抽奖请求。

    Mutex mutex = new Mutex();

    mutex.WaitOne();

    //...

    mutex.ReleaseMutex();

    以上三种锁都有WaitOne()方法,因为他们都继承自waitHandle

    读写锁ReaderWriterLock

    注意:读写锁并不是从限定线程个数的角度出发。而是按照读写的功能划分。

    读写锁的基本方案:多个线程可以一起读,只能让一个线程去写。

    模拟场景:多个线程读取,一个线程写。请思考:写的线程是否能够正常阻止读的线程?如果能阻止,则达到目标。

    static ReaderWriterLock readerWriterLock = new ReaderWriterLock();

    /// <summary>

    /// 读取数据的线程

    /// </summary>

    private static void ThreadRead()

    {

        while (true)

        {

            readerWriterLock.AcquireReaderLock(int.MaxValue);//参数:表示最大的超时时间

            Thread.Sleep(100);

            Console.WriteLine($"当前读取的tid={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");

            readerWriterLock.ReleaseReaderLock();

        }

    }

    /// <summary>

    /// 写入数据的线程

    /// </summary>

    private static void ThreadWrite()

    {

        while (true)

        {

            readerWriterLock.AcquireWriterLock(int.MaxValue);//参数:表示最大的超时时间

            Thread.Sleep(3000);

            Console.WriteLine($"---------------------------------------------当前写入的tid={Thread.CurrentThread.ManagedThreadId}  {DateTime.Now.ToLongTimeString()}");

            readerWriterLock.ReleaseWriterLock();

        }

    }

    //通过观察,我们发现写入的时候,能够正常的拦截读取的线程。

    //PS:如果我们写入数据的任务耗时太长,比如十几秒或更长,此时读的线程会被卡死,从而超时。开发中要特别注意。

    动态计数锁

    CountdownEvent:限制线程数的一个机制,而且这个也是比较常用的(同属于信号量的一种).

    使用场景:基于多个线程从某一个表中读取数据:比如我们现有ABC…每一张数据表我们都希望通过多个线程去读取。因为用一个线程的话,那么数据量大会出现卡死的情况。

    举例:

    A表:10w数据–》10个线程读取,1个线程1w条数据。

    B表:5w数据 --5个线程 1个线程1w

    C表:1w数据 --2个线程 1个线程5k

    private static CountdownEvent countdownEvent = new CountdownEvent(10);

    //默认10threadcount初始值,一个线程用一个就减掉1,直到为0后,相当于结束

    static void LoadData()

    {

        countdownEvent.Reset(10);//重置当前ThreadCount上限

        for (int i = 0; i < 10; i++)

        {

            Task.Factory.StartNew(() =>

                                  {

                                      Thread.Sleep(500);

                                      LoadTableA();

                                  });

        }

        //阻止当前线程,直到设置了System.Threading.CountdonwEvent为止

        countdownEvent.Wait();//相当于Task.WaitAll()

        Console.WriteLine("TableA加载完毕.......... ");

        //加载B

        countdownEvent.Reset(5);

        for (int i = 0; i < 5; i++)

        {

            Task.Factory.StartNew(() =>

                                  {

                                      Thread.Sleep(500);

                                      LoadTableB();

                                  });

        }

        countdownEvent.Wait();

        Console.WriteLine("TableB加载完毕.......... ");

        //加载C

        myLock7.Reset(2);

        for (int i = 0; i < 2; i++)

        {

            Task.Factory.StartNew(() =>

                                  {

                                      Thread.Sleep(500);

                                      LoadTableC();

                                  });

        }

        countdownEvent.Wait();

        Console.WriteLine("TableC加载完毕.......... ");

    }

    /// <summary>

    /// 加载A

    /// </summary>

    private static void LoadTableA()

    {

        //在这里编写具体的业务逻辑...

        Console.WriteLine($"当前TableA正在加载中...{Thread.CurrentThread.ManagedThreadId}");

        countdownEvent.Signal();//将当前的ThreadCount--   操作,就是减掉一个值

    }

    /// <summary>

    /// 加载B

    /// </summary>

    private static void LoadTableB()

    {

        //在这里编写具体的业务逻辑...

        Console.WriteLine($"当前TableB正在加载中...{ Thread.CurrentThread.ManagedThreadId}");

        countdownEvent.Signal();

    }

    /// <summary>

    /// 加载C

    /// </summary>

    private static void LoadTableC()

    {

        //在这里编写具体的业务逻辑...

        Console.WriteLine($"当前TableC正在加载中...{Thread.CurrentThread.ManagedThreadId}");

        countdownEvent.Signal();

    }

    监视锁

    Monitor 限制线程个数的一把锁。

    lock 关键字,本质是Monitor的语法糖

    ILSyp自己观察生成的代码,发现和Monitor是一样的

    lock/Monitor的内部机制,本质上就是利用对上的“同步块”实现资源锁定。

    PS:在前面挺多的锁中,只有Monitor有语法糖,说明这个重要。

    Monitor

    锁住的资源一定要让可访问的线程能够访问到,所以不能是局部变量。

    锁住的资源千万不要是值类型。

    lock 不能锁住string类型,虽然它是引用类型(这个可能存在疑问)。

    private static object syncRoot = new object();

    private int num;

    //1】简单写法

    static void TestMethod1()

    {

        for (int i = 0; i < 100; i++)

        {

            Monitor.Enter(syncRoot);//锁住资源

            num++;

            Console.WriteLine(num);

            Monitor.Exit(syncRoot);//退出资源

        }

    }

    //2】严谨的写法(更常用的写法)

    static void TestMethod2()

    {

        for (int i = 0; i < 100; i++)

        {

            bool taken = false;

            try

            {

                Monitor.Enter(syncRoot, ref taken);//这个类似于SpinLock

                num++;

                Console.WriteLine(num);

            }

            catch (Exception ex)

            {

                Console.WriteLine(ex.Message);

            }

            finally

            {

                if (taken)

                {

                    Monitor.Exit(syncRoot);

                }

            }

        }

    }

    //总结:为了严谨性,保证程序正常秩序,我们在锁区域添加了异常处理,还要添加判断,非常麻烦。我们可以使用语法糖Lock

    //语法糖:只是编译器层面的,底层代码生成还是跟以前一样的。

    static void Method11()

    {

        for (int i = 0; i < 100; i++)

        {

            lock (syncRoot)

            {

                num++;

                Console.WriteLine(num);

            }

        }

    }

    ————————————————

    版权声明:本文为CSDN博主「像像像橡树」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

    原文链接:https://blog.csdn.net/weixin_43989331/article/details/105356008

    参考网址: https://www.cnblogs.com/yy1234/p/7691075.html

     1)原子操作(Interlocked):所有方法都是执行一次原子读取或一次写入操作。

      2)lock()语句:避免锁定public类型,否则实例将超出代码控制的范围,定义private对象来锁定。

      3)Monitor实现线程同步

        通过Monitor.Enter() 和 Monitor.Exit()实现排它锁的获取和释放,获取之后独占资源,不允许其他线程访问。

        还有一个TryEnter方法,请求不到资源时不会阻塞等待,可以设置超时时间,获取不到直接返回false。

      4)ReaderWriterLock

        当对资源操作读多写少的时候,为了提高资源的利用率,让读操作锁为共享锁,多个线程可以并发读取资源,而写操作为独占锁,只允许一个线程操作。

      5)事件(Event)类实现同步

        事件类有两种状态,终止状态和非终止状态,终止状态时调用WaitOne可以请求成功,通过Set将时间状态设置为终止状态。

        1)AutoResetEvent(自动重置事件)

        2)ManualResetEvent(手动重置事件)

      6)信号量(Semaphore)

          信号量是由内核对象维护的int变量,为0时,线程阻塞,大于0时解除阻塞,当一个信号量上的等待线程解除阻塞后,信号量计数+1。

          线程通过WaitOne将信号量减1,通过Release将信号量加1,使用很简单。

      7)互斥体(Mutex)

          独占资源,用法与Semaphore相似。

       8)跨进程间的同步

          通过设置同步对象的名称就可以实现系统级的同步,不同应用程序通过同步对象的名称识别不同同步对象。

  • 相关阅读:
    看所访问网站的IP
    FLEX是什么及与FLASH的关系的介绍
    [设计模式]Head First Design Patterns[转]
    ASP.NET 2.0移动开发入门之使用模拟器
    在一篇文章的基础上总结了一下(接口,组合,继承,组件)
    抽象类与接口的区别 Java描述(转)
    C#:代表(delegate)和事件(event) (转)
    C#分页控件(自己做的)
    一个站点控制另一个站点的文件(添加,删除)用Webservices
    .net remoting程序开发入门篇
  • 原文地址:https://www.cnblogs.com/bruce1992/p/15240823.html
Copyright © 2011-2022 走看看