浅解c#多线程读写锁(一)最近发表过一些对c#多线程数据读写安全线的文章,有网友说都是代码不好理解,我在这里就给出我的一些解释,希望大家多多指较.这里我重复一下多线程数据 读写安全的观点:多线程下的数据安全应该指的是在使用数据的生存期内它是不变的,使用数据的生存期可以是一个过程或函数,当然这里的指的数据不包含过程或 函数中的局部变量,因为局部变量它本身就是线程安全的数据. 目标:确保数据在使用的生命期内是不变的. 解决思路:对于数额的使用不外乎就是读和写,而读操作对数据是不会产生变化的,仅有写操作才对数据产生变化,试想一下两个线程同时对同一数据进行读和写操 作,那么读出的数据很可能在使用过程中被写操作改变了,那么依据原先读出的数据进行的逻辑过程差之豪厘失之千理了.大家且看下面的一个例子,该例子的类有 这么一个逻辑,它有一个状态变量Disposed,当它为False时该类的所有方法可用,当它为True时该类的所有方法不可用,如果使用就出现错误. 而该类有一个方法Function1做一些操作,有一个方法Close关闭该类同时设置该类的状态变量为False. View Code
假设有两个线程正分别执行Function1和Close方法,当Function1方法执行到第8句时发现Disposed参数为False所以它继续 下面的操作,而此时Close方法已执行完所有清理工作并对Disposed做了设置,那么此时Function1里执行的方方法就肯定出错了(这也解答 了有网友说的对写同步读就没有必要的问题了),因为此时该类已进行过一系列的清理工作了.这也就是说要实现该类的逻辑并且线程安全,就必需对 Disposed变量进行同步,那么对该类进行如下更改: 用Lock同步的类
增加了Lock锁此时线程安全了,这是最简单的线程数据读写安全的方法,但是一个问题出来了Function1方法的使用是很频繁的(比如异步消息 接收),Close方法仅在不使用该类时调用一次,也就是说为了同步Disposed使得Function1在每次调用时都要等待上次调用结束才能进行否 则就阻塞在Lock语句中,这样一来多线程的优势就完全丧失了.那么该如何才能保持多线程的优势而又能使Disposed得到同步呢,采用读写锁,也就是 说只要存在读锁没有释放写锁的获取就一直阻塞直到所有读锁都释放,而只要有一个写锁没有释放所有锁(不管读还是写)的获取都要一直阻塞直到写锁释放.总的 来说读琐和写锁获取的逻辑条件如下: 成功获取读锁的充要条件是没有任何写锁. 成功获取写锁的充要条件是没有任何锁. 解决方案:设计一个类实现读写锁获取的充要条件,并且为了使用简捷考虑返回一个实现IDispose接口并且能指示是否成功获取的属性,如下面的样子: IDisposeState接口
该接口的IsValid属性指示是否成功获取锁.设计实现读写锁的类实现类似以下的接口已满足读写锁的获取的逻辑要求: IReadWriteLock
该接口的读写锁获取函数都返回前面定义的IDisposeState接口,对该接口的使用方法如下: 读写锁的使用
这样上面的那个例子类就在同步了Disposed参数的同时保持了多线程的优势了. 实现要点: 步骤一 锁定内部资源(排它锁) 步骤二 判断读写锁逻辑是否满足,如果满足则进行锁登记等等操作 步骤三 解除排它锁 步骤四 如果步骤二满足则返回有效锁,否则线程随机停顿一段时间后重新执行步骤一直到成功或超时 这里的关键点是排它锁的获取,由于它是不停的轮值询问使用的,所以它的实现要求使用资源少且速度快. 参考如下两个类,一个使用Lock,一个没有使用 使用Lock的排它锁
不使用Lock 不使用Lock的排它锁
这两个类都实现了排它锁的功能,都可以用在步骤一和三,由于该锁使用极其频繁所以我们比较一下这两个类的性能看看: 分别对这两个类循环调用Lock或UnLock方法得出如下结果 调用次数 Lock方法耗时(毫秒) UnLock方法耗时(毫秒) IntLock类 100000000 3390.625 3421.875 LockLock类 7000 7078.125 IntLock类 10000000 343.75 343.75 LockLock类 703.125 671.875 IntLock类 1000000 31.25 31.25 LockLock类 62.5 62.5 IntLock类 100000 0 0 LockLock类 15.625 15.625 从以上的结果看出IntLock类要比使用Lock的类(LockLock)速度要快一倍以上,所以应该采用IntLock这样的方案来构造排它锁的类. 先说这些了,有空我再接下去说,请大家批评指正. |
|