zoukankan      html  css  js  c++  java
  • C#多线程编程中的锁系统

    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

  • 相关阅读:
    各大互联网公司架构演进之路汇总
    Java工程师成神之路~(2018修订版)
    Java的并发编程中的多线程问题到底是怎么回事儿?
    深入理解多线程(五)—— Java虚拟机的锁优化技术
    Java中线程池,你真的会用吗?
    深入理解多线程(四)—— Moniter的实现原理
    深入理解多线程(三)—— Java的对象头
    深入理解多线程(二)—— Java的对象模型
    深入理解多线程(一)——Synchronized的实现原理
    css
  • 原文地址:https://www.cnblogs.com/mq0036/p/11528657.html
Copyright © 2011-2022 走看看