C# 热身:线程 (III) 下一页
结果计时器会一直滚动,因为a对象被锁住,除非完成Thread.Sleep(3000000)后才能进入到a共享区
由于以上的问题,微软还是建议我们使用一个私有的变量来锁定,由于私有变量外界无法访问,所以锁住话死锁的可能性大大下降了。
这样我们就能选择正确的“门”来进行锁住,但是可能还有一种可能也会造成死锁,就是在lock内部出现了问题,由于死锁非常复杂,我将在
今后的文章中专门写一篇关于死锁的文章来深入解释下死锁,所以这里就对死锁不深究了,这里大伙了解下lock的使用方法和注意事项就行了。
5.ReaderWriterLock
由于lock关键字对临界区(共享区)保护的非常周密,导致了一些功能可能会无法实现,假设我将某个查询功能放置在临界区中时,可能
当别的线程在查询临界区中的数据时,可能我的那个线程被阻塞了,所以我们期望锁能够达到以下功能
1 首先锁能细分为读锁和写锁
2 能够保证同时可以让多个线程读取数据
3 能保证同一时刻只有一个线程能进行写操作,也就是说,对于写操作,它必须拥有独占锁
4 能保证一个线程同一时刻只能拥有写锁或读锁中的一个
显然lock关键字无法满足我们的需求,还好微软想到了这点,ReaderWriterLock便隆重登场了ReaderWriterLock能够达到的效果是:
1. 同一时刻,它允许多个读线程同时访问临界区,或者允许单个线程进行写访问
2. 在读访问率很高,而且写访问率很低的情况下,效率最高,
3.它也满足了同一时刻只能获取写锁或读锁的要求。
4. 最为关键的是,ReaderWriterLock能够保证读线程锁和写线程锁在各自的读写队列中,当某个线程释放了写锁了,同时读线程队列中
的所有线程将被授予读锁,同样,当所有的读锁被释放时,写线程队列中的排队的下一个线程将被授予写锁,更直观的说,ReaderWriterLock
就是在这几种状态间来回切换
5 使用时注意每当你使用AcquireXXX方法获取锁时,必须使用ReleaseXXX方法来释放锁
6 ReaderWriterLock 支持递归锁,关于递归锁会在今后的章节详细阐述
7 在性能方面ReaderWriterLock做的不够理想,和lock比较差距明显,而且该类库中还隐藏些bug,有于这些原因,微软又专门重新写了个新
类ReaderWriterLockSilm来弥补这些缺陷。
8 处理死锁方面ReaderWriterLock为我们提供了超时的参数这样我们便可以有效的防止死锁
9 对于一个个获取了读锁的线程来说,在写锁空闲的情况下可以升级为写锁
接着让我们了解下ReaderWriterLock的重要成员
上述4个方法分别是让线程获取写锁和读锁的方法,它利用的计数的概念,当一个线程中调用此方法后,该类会给该线程拥有的锁计数加1
(每次加1,但是一个线程可以拥有多个读锁,所以计数值可能更多,但是对于写锁来说同时一个一个线程可以拥有)。后面的参数是超时
时间,我们可以自己设置来避免死锁。同样调用上述方法后我们必须使用ReleaseXXX 方法来让计数值减1,直到该线程拥有锁的计数为0,
释放了锁为止。
最后我们用一个简单的例子来温故下上述的知识点(请注意看注释)
/// <summary>
/// 该示例通过ReaderWriterLock同步来实现Student集合多线程下
/// 的写操作和读操作
/// </summary>
class Program
{
static ReaderWriterLock _readAndWriteLock = new ReaderWriterLock();
static List<Student> demoList = new List<Student>();
static void Main(string[] args)
{
InitialStudentList();
Thread thread=null;
for (int i = 0; i <5; i++)
{
//让第前2个个线程试图掌控写锁,
if (i < 2)
{
thread = new Thread(new ParameterizedThreadStart(AddStudent));
Console.WriteLine("线程ID:{0}, 尝试获取写锁 ", thread.ManagedThreadId);
thread.Start(new Student { Name = "Zhang" + i });
}
else
{
//让每个线程都能访问DisplayStudent 方法去获取读锁
thread = new Thread(new ThreadStart(DisplayStudent));
thread.Start();
}
Thread.Sleep(20);
}
Console.ReadKey();
}
static void InitialStudentList()
{
demoList = new List<Student> { new Student{ Name="Sun"}, new Student{Name="Zheng"} };
}
/// <summary>
/// 当多个线程试图使用该方法时,只有一个线程能够透过AcquireSWriterLock
/// 获取写锁,同时其他线程进入队列中等待,直到该线程使用ReleaseWriterLock后
/// 下个线程才能进入拥有写锁
/// </summary>
/// <param name="student"></param>
static void AddStudent(object student)
{
if (student == null|| !(student is Student)) return;
if (demoList.Contains(student)) return;
try
{
//获取写锁
_readAndWriteLock.AcquireWriterLock(Timeout.Infinite);
demoList.Add(student as Student);
Console.WriteLine("当前写操作线程为{0}, 写入的学生是:{1}", Thread.CurrentThread.ManagedThreadId,(student as Student).Name);
}
catch (Exception)
{
}
finally
{
_readAndWriteLock.ReleaseWriterLock();
}
}
/// <summary>
/// 对于读锁来所,允许多个线程共同拥有,所以这里同时
/// 可能会有多个线程访问Student集合,使用try catch是为了
/// 一定要让程序执行finally语句块中的releaseXXX方法,从而保证
/// 能够释放锁
/// </summary>
static void DisplayStudent()
{
try
{
_readAndWriteLock.AcquireReaderLock(Timeout.Infinite);
demoList.ForEach(student
=>
{
Console.WriteLine("当前集合中学生为:{0},当前读操作线程为{1}", student.Name, Thread.CurrentThread.ManagedThreadId);
});
}
catch (Exception)
{
}
finally
{
_readAndWriteLock.ReleaseReaderLock();
}
}
}
internal class Student
{
public string Name { get; set; }
}
运行结果:
从例子可以看出有2个线程试图尝试争取写锁,但是同时只有一个线程可以获取到写锁,同时对于读取集合的线程可以同时获取多个读锁
6. 本章总结
由于本人上个月工作突然忙了起来,快一个多月没更新博客了,希望大家可以见谅^^
本章介绍了线程同步的概念和一些关于同步非常重要的基本概念,对于原子性的操作的认识也格外重要,同时对于Volatile,Interlocked,lock,ReaderWriterLock 知识点做了相关介绍,
相信大家对于线程同步有个初步的认识和理解,在写本篇博客时,发现死锁也是个很重要的知识点,关于死锁我会单独写篇文章来阐述,谢谢大家的支持!
7. 参考资料
CLR via c#
msdn
上一篇: 鉴往知来--读《论语》 14
下一篇: 不变》周刊第 42 期