C#多线程编程中的锁系统(二)


上章主要讲排他锁的直接使用方式。但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了。 这一次我们说说升级锁和原子操作。

目录
1:volatile
2:  Interlocked
3:ReaderWriterLockSlim
4:总结

一:volatile

简单来说: volatile关键字是告诉c#编译器和JIT编译器,不对volatile标记的字段做任何的缓存。确保字段读写都是原子操作,最新值。

这不就是锁吗?   其这货它根本不是锁, 它的原子操作是基于CPU本身的,非阻塞的。 因为32位CPU执行赋值指令,数据传输最大宽度4个字节。

所以只要在4个字节以下读写操作的,32位CPU都是原子操作。volatile 它就是利用这个特性来的。

好残酷的事实?不然,微软大法这样是为了提高JIT性能效率,对有些数据进行缓存了(多线程下)。

复制代码 代码如下:
  //正确
       public volatile Int32 score1 = 1;
        //报错
        public volatile Int64 score2 = 1;

看上面的例子,我们定义8个字节长度score2就不行了。  因为8个字节,32位CPU就分成2个指令执行了。自然就无法保证原子操作了。

这么细节的,忘了怎么办,那岂不是坑人啊。  于是微软大法直接一棍子打死,限制4个字节以下的类型字段才能用volatile,具体什么、看msdn吧。

那今天我知道了。我编译平台改成64位上,只在64位CPU用volatile  int64,行不行?  不行,编译器报错。说了一棍子打死了。。

(^._.^)ノ  好吧,其实可以用IntPtr这个。

volatile多数情况下很有用处的,毕竟锁的性能开销还是很大的。我们可以把当成轻量级的锁,根据具体场景合理使用,能提高不少程序性能。

线程中的Thread.VolatileRead 和Thread.VolatileWrite 就是volatile的复杂版。

二:Interlocked

MSDN 描述:为多个线程共享的变量提供原子操作。主要函数如下:

Interlocked.Increment    原子操作,递增指定变量的值并存储结果。
Interlocked.Decrement       原子操作,递减指定变量的值并存储结果。
Interlocked.Add        原子操作,添加两个整数并用两者的和替换第一个整数

Interlocked.CompareExchange(ref a, b, c);  原子操作,a参数和c参数比较,  相等b替换a,不相等不替换。

基本用法就不多说了。直接来段CLR via C# interlock anything的例子:

复制代码 代码如下:
public static int Maximum(ref int target, int value)
        {
            int currentVal = target, startVal, desiredVal;  //记录前后值
            do
            {
                startVal = currentVal; //记录循环迭代的初始值。
                desiredVal = Math.Max(startVal, value); //基于startVal和value计算期望值desiredVal

//高并发下,线程被抢占情况下,target值会发生改变。

//target startVal相等说明没改变。desiredVal 直接替换。
                currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);

} while (startVal != currentVal); //不相等说明,target值已经被其他线程改动。自旋继续。
            return desiredVal;
        }

三:ReaderWriterLockSlim

假如我们有份缓存数据A,如果每次都不管任何操作lock一下,那么我的这份缓存A就永远只能单线程读写了, 这在Web高并发下是不能忍受的。

那有没有一种办法我只在写入时进入独占锁呢,读操作时不限制线程数量呢?答案就是我们的ReaderWriterLockSlim主角,读写锁。

ReaderWriterLockSlim 其中一种锁EnterUpgradeableReadLock最关键  即可升级锁。

它呢允许你先进入读锁,发现缓存A不一样了, 再进入写锁,写入后退回读锁模式。

ps: 这里注意下net 3.5之前有个ReaderWriterLock 性能较差。推荐使用升级版的 ReaderWriterLockSlim 。

复制代码 代码如下:
//实例一个读写锁
 ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

上面实例一个读写锁,这里注意的是构造函数的枚举。

LockRecursionPolicy.NoRecursion 不支持,发现递归会抛异常。

LockRecursionPolicy.SupportsRecursion  即支持递归模式,线程锁中继续在使用锁。

复制代码 代码如下:
cacheLock.EnterReadLock();
            //do
                cacheLock.EnterReadLock();
                //do
                cacheLock.ExitReadLock();
            cacheLock.ExitReadLock();

这种模式极易容易死锁,比如读锁里面使用写锁。

复制代码 代码如下:
cacheLock.EnterReadLock();
            //do
              cacheLock.EnterWriteLock();
              //do
              cacheLock.ExitWriteLock();
            cacheLock.ExitReadLock();

下面是直接拿msdn的缓存例子了,加了简单注释。

复制代码 代码如下:
public class SynchronizedCache
    {
        private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
        private Dictionary<int, string> innerCache = new Dictionary<int, string>();

public string Read(int key)
        {
            //进入读锁,允许其他所有的读线程,写入线程被阻塞。
            cacheLock.EnterReadLock();
            try
            {
                return innerCache[key];
            }
            finally
            {
                cacheLock.ExitReadLock();
            }
        }

public void Add(int key, string value)
        {
            //进入写锁,其他所有访问操作的线程都被阻塞。即写独占锁。
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }

public bool AddWithTimeout(int key, string value, int timeout)
        {
            //超时设置,如果在超时时间内,其他写锁还不释放,就放弃操作。
            if (cacheLock.TryEnterWriteLock(timeout))
            {
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return true;
            }
            else
            {
                return false;
            }
        }

public AddOrUpdateStatus AddOrUpdate(int key, string value)
        {
            //进入升级锁。 同时只能有一个可升级锁线程。写锁,升级锁都被阻塞,但允许其他读取数据的线程。
            cacheLock.EnterUpgradeableReadLock();
            try
            {
                string result = null;
                if (innerCache.TryGetValue(key, out result))
                {
                    if (result == value)
                    {
                        return AddOrUpdateStatus.Unchanged;
                    }
                    else
                    {
                        //升级成写锁,其他所有线程都被阻塞。
                        cacheLock.EnterWriteLock();
                        try
                        {
                            innerCache[key] = value;
                        }
                        finally
                        {
                            //退出写锁,允许其他读线程。
                            cacheLock.ExitWriteLock();
                        }
                        return AddOrUpdateStatus.Updated;
                    }
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache.Add(key, value);
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Added;
                }
            }
            finally
            {
                //退出升级锁。
                cacheLock.ExitUpgradeableReadLock();
            }
        }

public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        };
    }

四:总结

多线程实际开发当中,往往测试没问题,一到生产环境,并发高了就容易出问题, 一定注意。

本文参考CLR via C#。

 

出处:https://www.xp.cn/b.php/60358.html

C#多线程编程中的锁系统的更多相关文章

  1. c#语言-多线程中的锁系统(一)

    介绍 平常在多线程开发中,总避免不了线程同步.本篇就对net多线程中的锁系统做个简单描述.   目录 一:lock.Monitor        1:基础.        2: 作用域.       ...

  2. Java多线程编程(2)--多线程编程中的挑战

    一.串行.并发和并行   为了更清楚地解释这三个概念,我们来举一个例子.假设我们有A.B.C三项工作要做,那么我们有以下三种方式来完成这些工作:   第一种方式,先开始做工作A,完成之后再开始做工作B ...

  3. 关于python多线程编程中join()和setDaemon()的一点儿探究

    关于python多线程编程中join()和setDaemon()的用法,这两天我看网上的资料看得头晕脑涨也没看懂,干脆就做一个实验来看看吧. 首先是编写实验的基础代码,创建一个名为MyThread的  ...

  4. Java多线程编程中Future模式的详解

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  5. C++ 关于MFC多线程编程中的一些注意事项 及自定义消息的处理

    在多线程编程中,最简单的方法,无非就是利用 AfxBeginThread  来创建一个工作线程,看一下这个函数的说明: CWinThread* AFXAPI AfxBeginThread( AFX_T ...

  6. Java多线程编程中Future模式的详解<转>

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  7. Qt多线程编程中的对象线程与函数执行线程

    近来用Qt编写一段多线程的TcpSocket通信程序,被其中Qt中报的几个warning搞晕了,一会儿是说“Cannot create children for a parent that is in ...

  8. 【C/C++开发】多线程编程中的join函数

    多线程编程中的join函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # coding: utf-8 # 测试多线程中join的 ...

  9. 多线程中的锁系统(三)-WaitHandle、AutoResetEvent、ManualResetEvent

    本章主要介绍下基于内核模式构造的线程同步方式,事件,信号量. 阅读目录: 理论 WaitHandle AutoResetEvent ManualResetEvent 总结 理论 Windows的线程同 ...

随机推荐

  1. 某邀请赛misc key家用完整版

    目录 题目下载 提示 解题过程 1.提取RGB值 2.找到key 3.循环异或,得到flag 反思 题目下载 题目名:key 提示 提取钥匙中特殊颜色的RGB循环异或KEY值 解题过程 1.提取RGB ...

  2. CentOS忘记密码修改方案以及centos卡在开机登录界面,命令失效的解决方法

    CentOS忘记密码修改方案 应用场景 linux管理员忘记root密码,需要进行找回操作. 注意事项:本文基于CentOS7.2环境进行操作的,由于CentOS的版本之间是有差异的,继续之前请先确定 ...

  3. 关于linux 执行权限的理解

    关于linux 执行权限的理解他这个执行权限不是 执行什么命令的 是这个文件 是否能被执行的权限 比方说<pre>shell_exec('/home/crontabtest12.sh'); ...

  4. Selenium基础教程(二)环境搭建

    一.环境搭建 (1)初学者最佳环境: Python 2.7 + Selenium 2+ Firefox 46 (2)喜欢尝新的环境: Python 3.6 + Selenium 3+ Firefox ...

  5. 【转】潜说js对象和数组

    /* 数组和对象 [JavaScript 权威指南 第五版] */ /* 对象: 是一个无序属性集合, 每个属性都有自己的名字和值 */ /* 创建对象简单方法, 对象直接量 */ var obj = ...

  6. MOOC 编译原理笔记(一):编译原理概述以及程序设计语言的定义

    编译原理概述 什么是编译程序 编译程序指:把某一种高级语言程序等价地转换成另一张低级语言程序(如汇编语言或机器代码)的程序. 高级语言程序-翻译->机器语言程序-运行->结果. 其中编译程 ...

  7. 2、Vue构造器和扩展

    1.VUE构造器简介 VUE构造器是一个非常重要的语法. 每个Vue.js应用都是通过构造函数Vue创建一个根实例. New了Vue对象,然后调用了这个vue对象的构造器,并向构造器传入了数据. 在实 ...

  8. 冒泡排序法vs快速排序法

    闲着没事,想思考一下两种排序法的直观对比,写了个小程序,代码如下,分析见后面: class Program { static DateTime t1, t2; static TimeSpan ts1, ...

  9. ASP.NET SignalR 系列(九)之源码与总结

    1.SignalR 1.0与2.0有些不同,以上篇章均只支持2.0+ 2.必须注意客户端调用服务端对象和方法时的大小写问题 3.客户端上的方法不能重名 4.IE7及以下的,需要增加json的分析器,分 ...

  10. Django ForeignKey不需要参照完整性?

    我想在django模型中设置一个ForeignKey字段,它在某些时候指向另一个表.但我希望可以在这个字段中插入一个id,它引用另一个表中可能不存在的条目.因此,如果该行存在于另一个表中,我希望获得F ...