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#。

(0)

相关推荐

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

    本章主要说下基于内核模式构造的线程同步方式,事件,信号量. 目录 一:理论 二:WaitHandle 三:AutoResetEvent 四:ManualResetEvent 五:总结 一:理论 我们晓得线程同步可分为,用户模式构造和内核模式构造. 内核模式构造:是由windows系统本身使用,内核对象进行调度协助的.内核对象是系统地址空间中的一个内存块,由系统创建维护. 内核对象为内核所拥有,而不为进程所拥有,所以不同进程可以访问同一个内核对象, 如进程,线程,作业,事件,文件,信号量,互斥量等

  • Windows中使用C#为文件夹和文件编写密码锁的示例分享

    C#文件夹加锁小工具 用C#语言实现一个文件夹锁的程序,网上类似的"xxx文件夹xxx"软件很多,但是基本上都是C/C++语言实现的,且都没有提供源码(这个可以理解,毕竟是加密程序,不应该泄露源码). 程序的基本原理是:用C#语言重命名文件夹,通过重命名使之成为windows安全文件的类标识符.具体的方法是为文件夹添加拓展名".{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}" (.{2559a1f2-21d7-11d4-bdaf-00c

  • C#多线程编程中的锁系统(四):自旋锁

    目录 一:基础 二:自旋锁示例 三:SpinLock 四:继续SpinLock 五:总结 一:基础 内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式.用户模式构造和内核模式构造 优点:cpu利用最大化.它发现资源被锁住,请求就排队等候.线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求. 缺点:托管代码->用户模式代码->内核代码损耗.线程上下文切换损耗. 在锁的时间比较短时,系统频繁忙于休眠.切换,是个很大的性能损耗. 自旋锁:原子操作+自循环.通常说的用户构造模式.

  • C#多线程中如何运用互斥锁Mutex

    互斥锁(Mutex) 互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它. 互斥锁可适用于一个共享资源每次只能被一个线程访问的情况 函数: //创建一个处于未获取状态的互斥锁 Public Mutex(): //如果owned为true,互斥锁的初始状态就是被主线程所获取,否则处于未获取状态 Public Mutex(bool owned): 如果要获取一个互斥锁.应调用互斥锁上的WaitOne()方法,该方法继承于Thread.WaitHandle类 它处于等到状态直至所调用

  • 如何使用C#读写锁ReaderWriterLockSlim

    读写锁的概念很简单,允许多个线程同时获取读锁,但同一时间只允许一个线程获得写锁,因此也称作共享-独占锁.在C#中,推荐使用ReaderWriterLockSlim类来完成读写锁的功能. 某些场合下,对一个对象的读取次数远远大于修改次数,如果只是简单的用lock方式加锁,则会影响读取的效率.而如果采用读写锁,则多个线程可以同时读取该对象,只有等到对象被写入锁占用的时候,才会阻塞. 简单的说,当某个线程进入读取模式时,此时其他线程依然能进入读取模式,假设此时一个线程要进入写入模式,那么他不得不被阻塞

  • C#多线程编程中的锁系统基本用法

    平常在多线程开发中,总避免不了线程同步.本篇就对net多线程中的锁系统做个简单描述. 目录 一:lock.Monitor      1:基础.      2: 作用域.      3:字符串锁.      4:monitor使用 二:mutex 三:Semaphore 四:总结 一:lock.Monitor 1:基础 Lock是Monitor语法糖简化写法.Lock在IL会生成Monitor. 复制代码 代码如下: //======Example 1=====             strin

  • C#中lock死锁实例教程

    在c#中有个关键字lock,它的作用是锁定某一代码块,让同一时间只有一个线程访问该代码块,本文就来谈谈lock关键字的原理和其中应注意的几个问题: lock的使用原型是: lock(X) { //需要锁定的代码.... } 首先要明白为什么上面这段话能够锁定代码,其中的奥妙就是X这个对象,事实上X是任意一种引用类型,它在这儿起的作用就是任何线程执行到lock(X)时候,X需要独享才能运行下面的代码,若假定现在有3个线程A,B,C都执行到了lock(X)而ABC因为此时都占有X,这时ABC就要停下

  • C#实现将程序锁定到Win7任务栏的方法

    本文实例讲述了C#实现将程序锁定到Win7任务栏的方法.分享给大家供大家参考.具体实现方法如下: Win7Taskbar类: using System; using System.Collections.Generic; using System.Text; using Shell32; using System.IO; namespace TestWin7Taskbar { class Win7Taskbar { public static bool LockApp(bool isLock,

  • C#检查键盘大小写锁定状态的方法

    本文实例讲述了C#检查键盘大小写锁定状态的方法.分享给大家供大家参考.具体分析如下: 1.命名空间: using System.Runtime.InteropServices; 2.导入方法 [DllImport("user32.dll", EntryPoint = "GetKeyboardState")] public static extern int GetKeyboardState(byte[] pbKeyState); 3.大小写状态 public sta

  • C#解决SQlite并发异常问题的方法(使用读写锁)

    本文实例讲述了C#解决SQlite并发异常问题的方法.分享给大家供大家参考,具体如下: 使用C#访问sqlite时,常会遇到多线程并发导致SQLITE数据库损坏的问题. SQLite是文件级别的数据库,其锁也是文件级别的:多个线程可以同时读,但是同时只能有一个线程写.Android提供了SqliteOpenHelper类,加入Java的锁机制以便调用.但在C#中未提供类似功能. 作者利用读写锁(ReaderWriterLock),达到了多线程安全访问的目标. using System; usin

随机推荐