• 有关多线程环境下的Volatile、lock、Interlocked和Synchronized们


    • 📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
    • 📢本文作者:由webmote 原创
    • 📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !

    序言


    多线程下的变量访问,就如同脚踏几只船的海王,在其精细的时间管理下安排每一个女朋友约会,一不小心,就很可能打翻友谊的小船,彻底坠入无尽的大海深处…

    而为了让各位亲爱的猿们,在约会对象之间横跳的时候,能优雅的控制住频率,编程语言引入了多个关键字和对象类完成相关操作。

    让我们逐个看看,这些概念都能完成什么样的奇葩事件吧!

    1. Volatile 修饰符关键字

    volatile 关键字通常被用来表示一个字段的值很可能被多个线程修改,因此在编译器(VS)编译时不要进行优化,也不被缓存在编译器或硬件寄存器里。

    volatile 关键字,确保每次读取和写入时,其值都是直接从内存中拿出来的,避免任何的优化和缓存。

    volatile 关键字标识的信息,就如同海王的A女友信息,每次海王想知道A女友的信息时,都显示的是A的最新信息,而不是从其他人打探的过时信息。有了第一手的信息,才能最大程度的避免不慎翻船。

    让我们来个模拟例子吧,由于编译器的优化,准备这个例子着实不易。

    //让我们在.net6下测试下...
    Console.WriteLine("开始测试...");
    var test = new Test();
    new Thread(delegate () { Thread.Sleep(500); test.foo = 255; }).Start();
    while (test.foo != 255) ;
    Console.WriteLine("不好了,A女友正在抵达战场!");
    Console.ReadLine();
    
    public class Test
    {
        public int foo = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果你运行在Debug版本下,这时候你是可以收到A女友的抵达信息的。但是一旦你发布成Release,这个时候,命运的齿轮开始转动,你忽然收不到重要的抵达信息了,随着时间滴答滴答流动,危险的气息扑面而至。

    你也试试看,切换到Release版本,按Ctrl+F5, 界面如下:

    在这里插入图片描述
    这个时候,volatile关键字的重要性就体现出来了,我们修改下如下信息:

    public class Test
    {
        public volatile int foo = 0;
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    看吧,一个volatile,就救了你一条命。

    volatile的使用注意事项:

    • volatile关键字通常用于多线程应用程序中,用于处理由多个线程同时访问的共享字段。
    • volatile不用于同步;它仅确保单个读取和写入操作的可见性和原子性。如果需要同步来强制执行顺序或互斥,请考虑使用其他同步机制,如lock,Monitor,Semaphore
    • 在多线程方案中处理共享数据时,通常建议使用lock 关键字或其他原子操作类,因为仅使用volatile关键字可能不足以满足复杂的同步要求。
    • volatile`关键字用于字段修饰,一般常用的是整型、布尔、指针,当然还有引用类型(一般指地址)

    一般关闭线程的布尔值是最佳使用场景。

    单例的双重检查锁场景也是有用的,例如:

    public class Singleton {
    private static volatile Singleton _instance = null; 
    private static Object _locker = new Object();
    public static Singleton GetSingleValue()
    {
       if (_instance == null)
       {
           lock(_locker)
           {
              if (_instance == null)
              { _ instance = new Singleton(); }
           }
       }
       return _ instance;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    当然,有更简单的写法,那就是利用Lazy

    public class Singleton
    {
         private static readonly Lazy<Singleton> _instance
             = new Lazy<Singleton>(() => new Singleton());
        private Singleton()
        {
        }
         public static Singleton Instance
         {
             get
             {
                 return _instance.Value;
             }
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. Lock 锁,锁住要锁的人

    lock,是最好用的保护机制之一了。 锁住资源,让其他线程都在后面排队,这样就不会撞到一块了。

    在这里插入图片描述
    话说,海王的日程表,必须有锁,没有锁的海王都死翘翘了。

    这里是个简单的例子:

    private object mylock = new object();
    
    public int A {
    
      get {
        int result;
        lock(mylock) {
        result = mA; 
        }
        return result;
      } 
    
      set { 
         lock(mylock) { 
            mA = value; 
         }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    作为演示,这个例子足够简单;作为深度学习,这个例子并不好。

    大部分类的属性都不需要lock操作,使用 public DateTime CreatedTime{get;set;}就已经足够了。因为基础类型都是原子操作的,因此没必要去锁定,除非你在get,set里有更复杂的操作。

    因此,大可不必都增加上lock, 如果是多个线程访问,那么不妨增加上 volatile,当然,属性没法直接增加,有需要多写代码了。

    3.Interlocked 非锁的原子操作

    锁是独一无二的,那么对于时间管理大师们,来说,这并不是好消息。

    那么有什么其他办法,既能满足大师们同时多个骚操作,又能正常而及时的得到通知呢?那就不得不提Interlocked 了,经济实惠,的确是居家旅行必备良词。

    public class NuclearPowerPlant
    {
    	private long _meltdownIsHappening = 0;
    	public bool MeltdownIsHappeningRightNow 
    	{
    		get
    		{
    			/* 锁定操作仅仅支持整型,那么我们使用它替换布尔。
    			 */
    			return Interlocked.Read(ref _meltdownIsHappening) == 1;
    		}
    		set
    		{
    			Interlocked.Exchange(ref _meltdownIsHappening, Convert.ToInt64(value));
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这效率,嘎嘎的高。

    注意 Interlocked.Increment(ref this.counter); 在实现上,等同于lock(this.locker) this. Counter++;,不过效率吗,那是翻了几倍。可惜的是好东西总有限制。Interlocked仅仅支持整数类型。

    4. Synchronized 同步操作

    Synchronized 关键字,总有点像从哪里抄过来的,因此,这个用法并不常见。
    不过它的含义倒是很清晰,就是同一时刻仅允许一个线程访问。

    代码如下:

    public class Test
    {
        public volatile int foo = 0;
    
        [MethodImpl(MethodImplOptions.Synchronized)]
        public int Add(int a)
        {
            return foo + a;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    MethodImpl(MethodImplOptions.Synchronized)这个属性的实现,也很简单,就是粗暴的lock(this)

    因此,不建议直接使用。

    在这里插入图片描述

    总结

    哦哦哦,好像意犹未尽,不过时间有限,先到此为止吧。

    👓都看到这了,还在乎点个赞吗?

    👓都点赞了,还在乎一个收藏吗?

    👓都收藏了,还在乎一个评论吗?

  • 相关阅读:
    Python基本语法
    QT下的QThread多线程
    【vue】生命周期
    用户态切换到内核态的方式
    Python自动化之跨平台GUI利器PyAutoGUI
    【记录】Python3|Selenium 下载 PDF 不预览不弹窗(2024年)
    条码二维码读取设备在医疗设备自助服务的重要性
    学生护眼灯怎么选择?推荐学生护眼台灯十大名牌排行榜
    flex布局列表页(一行内容比较多,长度比较长)
    【虚拟内存机制】
  • 原文地址:https://blog.csdn.net/webmote/article/details/134091406