.net的锁
一、lock 、Monitor
处理并行任务的时候,效率最高的就是多线程。当不同线程需要访问同一资源时候,就需要同步了。就像生活中很多人要一起赶飞机大家都要访问飞机这个资源每个人是一条线程那么就有门,有了门就代表每次只能一位其他人都要排队进入。
Monitor 类控制对对象的访问通过授予为单线程的对象的锁。对象锁提供的功能来限制对代码通常称为关键节的块的访问。一个线程拥有对象的锁,而没有其他线程可以获取该锁。您还可以使用 Monitor 类,以确保没有其他线程有权访问的应用程序一部分的代码正在执行的锁的所有者,除非其他线程正在执行使用不同的锁定的对象的代码。
lock(object obj) //监视obj 是否有被使用或者lock,如果没有就有我使用,否则一直等待obj被释放。
{
......
}//释放obj lock(this) //锁住当前对象 将会锁住当前类的实例 不能在静态资源中使用
{
......
}//释放this
lock 就是下面代码的语法糖
try
{
Monitor.Enter(this);
XXX你的代码 }
finally
{
Monitor.Exit(this);
}
demo
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; public class Example
{
public static void Main()
{
List<Task> tasks = new List<Task>();
Random rnd = new Random();
long total = ;
int n = ; for (int taskCtr = ; taskCtr < ; taskCtr++)
tasks.Add(Task.Run( () => { int[] values = new int[];
int taskTotal = ;
int taskN = ;
int ctr = ;
Monitor.Enter(rnd);
// Generate 10,000 random integers
for (ctr = ; ctr < ; ctr++)
values[ctr] = rnd.Next(, );
Monitor.Exit(rnd);
taskN = ctr;
foreach (var value in values)
taskTotal += value; Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, (taskTotal * 1.0)/taskN,
taskN);
Interlocked.Add(ref n, taskN);
Interlocked.Add(ref total, taskTotal);
} ));
try {
Task.WaitAll(tasks.ToArray());
Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0)/n, n); }
catch (AggregateException e) {
foreach (var ie in e.InnerExceptions)
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);
}
}
}
// The example displays output like the following:
// Mean for task 1: 499.04 (N=10,000)
// Mean for task 2: 500.42 (N=10,000)
// Mean for task 3: 499.65 (N=10,000)
// Mean for task 8: 502.59 (N=10,000)
// Mean for task 5: 502.75 (N=10,000)
// Mean for task 4: 494.88 (N=10,000)
// Mean for task 7: 499.22 (N=10,000)
// Mean for task 10: 496.45 (N=10,000)
// Mean for task 6: 499.75 (N=10,000)
// Mean for task 9: 502.79 (N=10,000)
//
// Mean for all tasks: 499.75 (N=100,000)
二、ReaderWriterLock 类
定义支持单个写线程和多个读线程的锁。Microsoft 官方不推荐使用该类。Jeffrey Richter也在他的《CLR via C#》一书中对它进行了严厉的批判。
性能:这个类实在是太慢了。比如它的 AcquireReaderLock 方法比 Monitor 类的 Enter 方法要慢 5 倍左右,而等待争夺写锁甚至比 Monitor 类慢 6 倍。
策略:假如某个线程完成写入操作后,同时面临读线程和写线程等待处理。ReaderWriterLock会优先释放读线程,而让写线程继续等待。但我们使用读写锁是因为存在大量的读线程和非常少的写线程,这样写线程很可能必须长时间地等待,造成写线程饥饿,不能及时更新数据。更槽糕的情况是,假如写线程一直等待,就会造成活锁。反之,我们让 ReaderWriterLock采取写线程优先的策略。如果存在多个写线程,而读线程数量稀少,也会造成读线程饥饿。幸运的是,现实实践中,这种情况很少出现。一旦发生这种情况,我们可以采取互斥锁的办法。
写现成饥渴如下图
递归:ReaderWriterLock类支持锁递归。这就意味着该锁清楚的知道目前哪个线程拥有它。假如拥有该锁的线程递归尝试获得该读写锁,递归算法允许该线程获得该读写锁,并且增加获得该锁的计数。然而该线程必须释放该锁相同的次数以便线程不再拥有该锁。尽管这看起来是个很好的特性,但是实现这个“特性”代价太高。首先,因为多个读线程可以同时拥有该读写锁,这必须让该锁为每个线程保持计数。此外,还需要额外的内存空间和时间来更新计数。这个特性对 ReaderWriterLock类可怜的性能贡献极大。其次,有些良好的设计需要一个线程在此处获得该锁,然后在别处释放该锁(比如 .NET 的异步编程架构)。因为这个递归特性,ReaderWriterLock 不支持这种编程架构。
资源泄漏:在 .NET 2.0 之前的版本中, ReaderWriterLock 类会造成内核对象泄露。这些对象只有在进程终止后才能再次回收。幸运的是,.NET 2.0 修正了这个 Bug 。
此外:ReaderWriterLock 还有个令人担心的危险的非原子性操作。它就是 UpgradeToWriteLock方法。这个方法实际上在更新到写锁前先释放了读锁。这就让其他线程有机会在此期间乘虚而入,从而获得读写锁且改变状态。如果先更新到写锁,然后释放读锁。假如两个线程同时更新将会导致另外一个线程死锁。
所以 Microsoft 决定构建一个新类来一次性解决上述所有问题,这就是ReaderWriterLockSlim 类。本来可以在原有的 ReaderWriterLock 类上修正错误,但是考虑到兼容性和已存在的API ,Microsoft 放弃了这种做法。当然也可以标记 ReaderWriterLock 类为Obsolete,但是由于某些原因,这个类还有存在的必要。
Reader-Reader,第二个不需等待,直接获得读控制权;
Reader-Writer,第二个需要等待第一个调用释放读控制权后,才能获得写控制权;
Writer-Writer,第二个需要等待第一个调用释放写控制权后,才能获得写控制权;
Writer-Reader,第二个需要等待第一个调用释放写控制权后,才能获得读控制权。
重要事项 |
---|
.NET Framework 具有两个读取器 / 编写器锁, ReaderWriterLockSlim 和 ReaderWriterLock。 ReaderWriterLockSlim 建议将所有新的开发的。 ReaderWriterLockSlim 类似于 ReaderWriterLock, ,只是简化了递归、 升级和降级锁定状态的规则。 ReaderWriterLockSlim 可避免潜在的死锁的很多情况。 此外,性能的 ReaderWriterLockSlim 明显优于 ReaderWriterLock。 |
三、ReaderWriterLockSlim 类
新的ReaderWriterLockSlim 类支持三种锁定模式:Read,Write,UpgradeableRead。这三种模式对应的方法分别是EnterReadLock,EnterWriteLock,EnterUpgradeableReadLock 。再就是与此对应的TryEnterReadLock,TryEnterWriteLock,TryEnterUpgradeableReadLock,ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。Read 和 Writer 锁定模式比较简单易懂:Read 模式是典型的共享锁定模式,任意数量的线程都可以在该模式下同时获得锁;Writer模式则是互斥模式,在该模式下只允许一个线程进入该锁。UpgradeableRead锁定模式可能对于大多数人来说比较新鲜,但是在数据库领域却众所周知。
这个新的读写锁类性能跟 Monitor 类大致相当,大概在Monitor 类的 2倍之内。而且新锁优先让写线程获得锁,因为写操作的频率远小于读操作。通常这会导致更好的可伸缩性。起初,ReaderWriterLockSlim类在设计时考虑到相当多的情况。比如在早期 CTP 的代码还提供了PrefersReaders,PrefersWritersAndUpgrades 和 Fifo 等竞争策略。但是这些策略虽然添加起来非常简单,但是会导致情况非常的复杂。所以Microsoft 最后决定提供一个能够在大多数情况下良好工作的简单模型。
ReaderWriterLockSlim 的更新锁
现在让我们更加深入的讨论一下更新模型。UpgradeableRead 锁定模式允许安全的从 Read 或 Write 模式下更新。还记得先前ReaderWriterLock的更新是非原子性,危险的操作吗(尤其是大多数人根本没有意识到这点)?现在提供的新读写锁既不会破坏原子性,也不会导致死锁。新锁一次只允许一个线程处在 UpgradeableRead 模式下。
一旦该读写锁处在 UpgradeableRead模式下,线程就能读取某些状态值来决定是否降级到 Read 模式或升级到 Write 模式。注意应当尽可能快的作出这个决定:持有UpgradeableRead 锁会强制任何新的读请求等待,尽管已存在的读取操作仍然活跃。遗憾的是,CLR 团队移除了DowngradeToRead 和 UpgradeToWrite 两个方法。如果要降级到读锁,只要简单的在ExitUpgradeableReadLock 方法后紧跟着调用 EnterReadLock 方法即可:这可以让其他的 Read 和UpgradeableRead 获得完成先前应当持有却被 UpgradeableRead 锁持有的操作。如果要升级到写锁,只要简单调用EnterWriteLock 方法即可:这可能要等待,直到不再有任何线程在 Read 模式下持有锁。不像降级到读锁,必须调用ExitUpgradeableReadLock。在 Write 模式下不必非得调用ExitUpgradeableReadLock。但是为了形式统一,最好还是调用它。
using System;
using System.Linq;
using System.Threading;
namespace Lucifer.CSharp.Sample
{
class Program
{
private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
void Sample()
{
bool isUpdated = true;
rwLock.EnterUpgradeableReadLock();
try
{
if (/* … 读取状态值来决定是否更新 … */)
{
rwLock.EnterWriteLock();
try
{
//… 写入状态值 …
}
finally
{
rwLock.ExitWriteLock();
}
}
else
{
rwLock.EnterReadLock();
rwLock.ExitUpgradeableReadLock();
isUpdated = false;
try
{
//… 读取状态值 …
}
finally
{
rwLock.ExitReadLock();
}
}
}
finally
{
if (isUpdated)
rwLock.ExitUpgradeableReadLock();
}
}
}
}
ReaderWriterLockSlim 的递归策略
新的读写锁还有一个有意思的特性就是它的递归策略。默认情况下,除已提及的降级到读锁和升级到写锁之外,所有的递归请求都不允许。这意味着你不能连续两次调用 EnterReadLock,其他模式下也类似。如果你这么做,CLR 将会抛出 LockRecursionException异常。当然,你可以使用 LockRecursionPolicy.SupportsRecursion的构造函数参数让该读写锁支持递归锁定。但不建议对新的开发使用递归,因为递归会带来不必要的复杂情况,从而使你的代码更容易出现死锁现象。
有一种特殊的情况永远也不被允许,无论你采取什么样的递归策略。这就是当线程持有读锁时请求写锁。Microsoft 曾经考虑提供这样的支持,但是这种情况太容易导致死锁。所以 Microsoft 最终放弃了这个方案。
此外,这个新的读写锁还提供了很多对应的属性来确定线程是否在指定模型下持有该锁。比如 IsReadLockHeld,IsWriteLockHeld 和 IsUpgradeableReadLockHeld 。你也可以通过WaitingReadCount,WaitingWriteCount 和 WaitingUpgradeCount等属性来查看有多少线程正在等待持有特定模式下的锁。CurrentReadCount属性则告知目前有多少并发读线程。RecursiveReadCount, RecursiveWriteCount 和RecursiveUpgradeCount 则告知目前线程进入特定模式锁定状态下的次数。
小结
这篇文章分析了.NET 中提供的两个读写锁类。然而 .NET 3.5 提供的新读写锁 ReaderWriterLockSlim 类消除了ReaderWriterLock 类存在的主要问题。与 ReaderWriterLock相比,性能有了极大提高。更新具有原子性,也可以极大避免死锁。更有清晰的递归策略。在任何情况下,我们都应该使用ReaderWriterLockSlim 来代替 ReaderWriterLock 类。
Update 于 2008-12-07 0:06
Windows Vista 及其以后的版本新增了一个 SRWLock 原语。它以 Windows 内核事件机制为基础而构建。它的设计比较有意思。
SRW 锁不支持递归。Windows Kernel 团队认为支持递归会造成额外系统开销,原因是为了维持准确性需进行逐线程的计数。SRW锁也不支持从共享访问升级到独占访问,同时也不支持从独占访问降级到共享访问。支持升级能力可能会造成难以接受的复杂性和额外系统开销,这种开销甚至会影响锁内共享和独占获得代码的常见情况。它还要求定义关于如何选择等待中的读取器、等待中的写入器和等待升级的读取器的策略,这又将与无偏向的基本设计目标相抵触。我对其进行了 .NET 封装。
using System;
using System.Threading;
using System.Runtime.InteropServices;
namespace Lucifer.Threading.Lock
{
/// <summary>
/// Windows NT 6.0 才支持的读写锁。
/// </summary>
/// <remarks>请注意,这个类只能在 NT 6.0 及以后的版本中才能使用。</remarks>
public sealed class SRWLock
{
private IntPtr rwLock;
/// <summary>
/// 该锁不支持递归。
/// </summary>
public SRWLock()
{
InitializeSRWLock(out rwLock);
}
/// <summary>
/// 获得读锁。
/// </summary>
public void EnterReadLock()
{
AcquireSRWLockShared(ref rwLock);
}
/// <summary>
/// 获得写锁。
/// </summary>
public void EnterWriteLock()
{
AcquireSRWLockExclusive(ref rwLock);
}
/// <summary>
/// 释放读锁。
/// </summary>
public void ExitReadLock()
{
ReleaseSRWLockShared(ref rwLock);
}
/// <summary>
/// 释放写锁。
/// </summary>
public void ExitWriteLock()
{
ReleaseSRWLockExclusive(ref rwLock);
}
[DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
private static extern void InitializeSRWLock(out IntPtr rwLock);
[DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
private static extern void AcquireSRWLockExclusive(ref IntPtr rwLock);
[DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
private static extern void AcquireSRWLockShared(ref IntPtr rwLock);
[DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
private static extern void ReleaseSRWLockExclusive(ref IntPtr rwLock);
[DllImport("Kernel32", CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
private static extern void ReleaseSRWLockShared(ref IntPtr rwLock);
}
}
读写锁有个很常用的场景就是在缓存设计中。因为缓存中经常有些很稳定,不太长更新的内容。MSDN 的代码示例就很经典,我原版拷贝一下,呵呵。代码示例如下:
using System;
using System.Threading;
namespace Lucifer.CSharp.Sample
{
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 void Delete(int key)
{
cacheLock.EnterWriteLock();
try
{
innerCache.Remove(key);
}
finally
{
cacheLock.ExitWriteLock();
}
}
public enum AddOrUpdateStatus
{
Added,
Updated,
Unchanged
};
}
}
摘抄转载方便自己记忆免得到处找
原文:http://blog.csdn.net/zengjibing/archive/2009/02/22/3923168.aspx
.net的锁的更多相关文章
- 使用redis构建可靠分布式锁
关于分布式锁的概念,具体实现方式,直接参阅下面两个帖子,这里就不多介绍了. 分布式锁的多种实现方式 分布式锁总结 对于分布式锁的几种实现方式的优劣,这里再列举下 1. 数据库实现方式 优点:易理解 缺 ...
- 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
- java中的锁
java中有哪些锁 这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够.于是再次翻看了一下书里的内容,突然有点打开脑门的感觉.看来确实是要学习的最好方式 ...
- 分布式锁1 Java常用技术方案
前言: 由于在平时的工作中,线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性的问题,那么就要利用分布式锁来解决这些问题.所以自己结合实际工作中的一些经验和网上看到的一些资 ...
- 如何在高并发环境下设计出无锁的数据库操作(Java版本)
一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...
- 如何定位Oracle数据库被锁阻塞会话的根源
首先再次明确下,数据库因为要同时保证数据的并发性和一致性,所以操作有锁等待是正常的. 只有那些长时间没有提交或回滚的事物,阻塞了其他业务正常操作,才是需要去定位处理的. 1.单实例环境 2.RAC环境 ...
- java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)
一.Condition 类 在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.c ...
- Android 死锁和重入锁
死锁的定义: 1.一般的死锁 一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态,这就是一般死锁的定义. package com.cxt.t ...
- Xcode 锁终端
锁终端 输入: <1>cd /Applications/Xcode.app 回车 结果显示: Xcode.app 输入: <2>sudo chown -hR root:whee ...
- mysql 行级锁的使用以及死锁的预防
一.前言 mysql的InnoDB,支持事务和行级锁,可以使用行锁来处理用户提现等业务.使用mysql锁的时候有时候会出现死锁,要做好死锁的预防. 二.MySQL行级锁 行级锁又分共享锁和排他锁. 共 ...
随机推荐
- RYU 灭龙战 second day(内容大部分引自网络)
RYU 灭龙战 second day(内容大部分引自网络) 写好的markdown重启忘了保存...再写一次RLG 巨龙的稀有装备-RYU代码结构 RYU控制器代码结构的总结 RYU入门教程 RYU基 ...
- Window环境下RabbitMQ 添加用户、设置角色和权限
基本上新增用户.角色和权限的方法都一样,大概如下: REM 添加一个帐号 密码 rabbitmqctl.bat add_user zhangfujun zhangfujun123 REM 添加角色 r ...
- 制作U盘启动盘并重装系统
进入网站 http://www.msdn.hk/6/209/ 在列表中选择自己需要的系统,比如win7_64,则可以选择下图系统:Windows 7 Ultimate with Service Pac ...
- Docker(三)-Docker中Image、Container与Volume的迁移
Image 镜像的迁移,适用于离线环境. 一般离线环境,都会自建Docker Registry. 无论 官方的 ,还是最近流行的 Harbor ,都是不错的选择. 但是,这个世界上就是有些环境,或者说 ...
- [转帖][Bash Shell] Shell学习笔记
[Bash Shell] Shell学习笔记 http://www.cnblogs.com/maybe2030/p/5022595.html 阅读目录 编译型语言 解释型语言 5.1 作为可执行程序 ...
- php面试问答
结合实际PHP面试,汇总自己遇到的问题,以及网上其他人遇到的问题,尝试提供简洁准确的答案 包含MySQL.Redis.Web.安全.网络协议.PHP.服务器.业务设计.线上故障.个人简历.自我介绍.离 ...
- PostgreSQL之oracle_fdw安装与使用
目的介绍 现在项目开发遇到一个问题,就是需要从PostgreSQL中访问Oracle数据库 身为渣渣猿一脸懵逼.于是乎请教了公司的数据库方面的大牛韩工.告诉我用oracle_fdw 可以实现,但是在实 ...
- Mybatis中jdbcType和javaType、typeHandler的对照关系
JdbcType与Oracle.MySql数据类型对应列表,及 JdbcType Oracle MySql CHAR CHAR CHAR VARCHAR VARCHAR VARCHAR LONGV ...
- aop 例外通知就是记录业务方法出现错误 并保存到日志里面的功能
aop 例外通知就是记录业务方法出现错误 并保存到日志里面的功能
- Again Prime? No Time. UVA - 10780(质因子分解)
m^k就是让m的每个质因子个数都增加了k倍 求m的质因子 在n!中增加了多少倍就好了,因为m^k 表示每一个质因子增加相同的倍数k 所以我们需要找到增加倍数最小的那个..短板效应 其它质因子多增加 ...