重庆熊猫 Loading

.NET中线程锁的使用

更新记录
本文迁移自Panda666原博客,原发布时间:2021年7月1日。

一、说明

由于经常需要在多线程代码中使用Monitor进行同步,并且需要自己去手写try/finally块。因此C#提供了一个特殊的lock关键字来简化这个流程,本质就是lock是Monitor的语法糖。
lock关键字将语句块标记为一个代码区域。确保只有一个线程位于该区域,其他线程不可以进入该区域(不会被其他线程中断)。如果其他线程要访问该区域,将一直等待,直到该区域被占用线程释放,意思就是说该区域会进行加一个互斥锁,执行完成后才会释放。

二、使用语法

lock(expression)

或者

lock(expression)
{
    //互斥的代码
}

说明:
expression可以是一个类的实例(引用变量),表示保护一个类的实例。expression可以是一个静态变量,表示保护一个静态变量。提供给lock语句的参数必须为基于引用类型的对象,该对象用来定义锁的范围。严格地说,提供给lock语句的参数只是用来唯一标识由多个线程共享的资源,所以可以是任意类实例。

通常,最好避免锁定public类型或不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则lock(this)可能会有问题。因为不受控制的代码也可能会锁定该对象,这将可能导致死锁,即两个或更多个线程等待释放同一个对象。 出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题,锁定字符串 尤其危险。因为字符串被公共语言运行库(CLR)“暂留”,这意味着整个程序中 任何给定字符串都只有一个实例。最好锁定不会被暂留的私有或受保护成员。

实例:

using System.Threading;
public class PandaThreadTest
{
    //锁定的对象
    private readonly static object _Sync = new object();
    //计数的总数
    private const int _Total = 1000;
    //计数的变量
    private int _Count = 0;

    //增加操作
    public void Increment()
    {
        lock(_Sync)
        {
            //增加操作
            for (int i = 0; i < _Total; i++)
            {
                this._Count++;
                Console.WriteLine("增加操作{0}", this._Count);
            }
        }
    }

    //减少操作
    public void Decrement()
    {
        //使用lock关键字
        lock(_Sync)
        {
            //减少操作
            for (int i = 0; i < _Total; i++)
            {
                _Count--;
                Console.WriteLine("减少操作{0}", this._Count);
            }
        }
    }

    //模拟多线程操作
    public void Test()
    {
        //新建线程
        Thread thread1 = new Thread(this.Increment);
        //开启线程
        thread1.Start();

        Thread thread2 = new Thread(this.Decrement);
        //开启线程
        thread2.Start();

        //等待线程1完成
        thread1.Join();

        //等待线程2完成
        thread2.Join();

        Console.WriteLine("执行完成");
        Console.WriteLine("_Count={0}",this._Count);
    }
}

//进行线程的处理
PandaThreadTest pandaThreadTest = new PandaThreadTest();
//测试多线程反应
pandaThreadTest.Test();

三、如何选择lock的对象(Choosing a lock Object)

1、应确保在多线程的操作过程中不会变化比如Monitor.Enter()、Monitor.Exit()的使用中不会发生变化。否则在同步代码块的进入和退出之间将没有任何关联。

2、lock的对象不可以是值类型。如果使用值类型,运行时会报错。本质是将值类型进行装箱操作。因为Enter和Exit的装箱引用不同,different synchronization object instances,所以会导致没有任何关联出错。

四、lock本质

事实上lock语句是用Monitor类来实现的,它等效于try/finally语句块。

使用lock关键字通常比直接使用Monitor类更可取,一方面是因为lock更简洁,另一方面是因为lock确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过finally关键字来实现的,无论是否引发异常它都执行关联的代码块。

posted @ 2022-04-16 16:44  重庆熊猫  阅读(249)  评论(0编辑  收藏  举报