C# 线程同步(6)- 读写锁 ReaderWriterLock

  • 到这一篇,在Windows下主流的线程同步方法已经都讲过了,包括穿插提到的Interlocked类,那都是我们传统的曾经学到过的概念。除此之外,.Net提供了一些特有的东西来帮助我们方便地完成代码,于是便有这一篇中要讨论的读写锁。

    ReaderWriterLock锁的好处

      它跟Monitor一样,是.Net的原生类,不再与操作系统有什么瓜葛。回想Monitor、EventWaitHandle两篇中,关于生产者、消费者和糖罐的例子,无论是一个消费者一个生产者、还是一个消费者和多个生产者,由于使用Monitor/lock的原因,一个时刻总是只有一个线程在对糖罐进行互斥的访问。这样其实会对吞吐量造成影响,如果有一个对实时性要求比较高的场景,在各种处理线程增加到一等数目后,处理速度的瓶颈就可能变为对资源的互斥访问上。

      在某些场景里,多个并发的读访问并不会有什么问题,这就是ReaderWriterLock针对Monitor改进之处。以下摘自MSDN:

    ReaderWriterLock 用于同步对资源的访问。在任一特定时刻,它允许多个线程同时进行读访问,或者允许单个线程进行写访问。在资源不经常发生更改的情况下,ReaderWriterLock 所提供的吞吐量比简单的一次只允许一个线程的锁(如 Monitor)更高。

    在多数访问为读访问,而写访问频率较低、持续时间也比较短的情况下,ReaderWriterLock 的性能最好。多个读线程与单个写线程交替进行操作,所以读线程和写线程都不会长时间阻止。

    注意
    长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
     
    一个线程可以持有读线程锁或写线程锁,但是不能同时持有两者。若要获取写线程锁,请使用 UpgradeToWriterLock 和 DowngradeFromWriterLock,而不要通过释放读线程锁的方式获取。

    递归锁请求会增加锁上的锁计数。

    读线程和写线程将分别排入各自的队列。当线程释放写线程锁时,此刻读线程队列中的所有等待线程都将被授予读线程锁;当已释放所有读线程锁时,写线程队列中处于等待状态的下一个线程(如果存在)将被授予写线程锁,依此类推。换句话说,ReaderWriterLock 在一组读线程和一个写线程之间交替进行操作。

    当写线程队列中有一个线程在等待活动读线程锁被释放时,请求新的读线程锁的线程会排入读线程队列。即使它们能和现有的阅读器锁持有者共享并发访问,也不会给它们的请求授予权限;这有助于防止编写器被阅读器无限期阻止。

    大多数在 ReaderWriterLock 上获取锁的方法都采用超时值。使用超时可以避免应用程序中出现死锁。例如,某个线程可能获取了一个资源上的写线程锁,然后请求第二个资源上的读线程锁;同时,另一个线程获取了第二个资源上的写线程锁,并请求第一个资源上的读线程锁。如果不使用超时,这两个线程将出现死锁。

    如果超时间隔过期并且没有授予锁请求,则此方法通过引发 ApplicationException 将控制返回给调用线程。线程可以捕捉此异常并确定下一步要进行的操作。

      这段描述还算比较清楚,不会给人带来太多困惑,我只是想提醒几点:

    • 请确信在你的使用场景中,读的并发访问是允许的。我们之前的生产者、消费者和糖罐的例子并不适合使用ReadWriterLock,因为生产者和消费者都在“写”糖罐,只是一个插入一个删除而已。
    • 这里的并发只是针对读操作,读写本身还是互斥的。在读锁被获取时是无法得到写锁的,反之亦然。所以它适合于“写访问频率较低、持续时间也比较短的情况”。如果写时间较长,也就意味着对这个资源总的(读和写)访问频率较低,那么本来也就没有吞吐量低的问题了。如果出现这种状况,你可以尝试把占用时间的读写操作再次安排到其它工作线程中去,尽量缩短对资源的占用时间。
    • 微软竟然对超时采用抛出异常的方式,并且居然说“可以捕捉此异常并确定下一步要进行的操作”……你见过用异常控制程序流程的设计吗?!(我对微软的抱怨是不是太多了?)

    ReaderWriterLock的使用方法

      好了,让我们继续上一篇言简意赅的偷懒风格,浏览下ReaderWriterLock的主要方法:

    • AcquireReaderLock():获取读线程锁。
    • AcquireWriterLock():获取写线程锁。
    • ReleaseReaderLock():减少锁计数,计数到达零时释放锁。ReleaseReaderLock将减少锁计数。如果线程持有写线程锁,调用 ReleaseReaderLock 与调用 ReleaseWriterLock 具有相同的效果。如果线程没有锁,调用 ReleaseReaderLock 会引发 ApplicationException。
    • ReleaseWriterLock():将减少写线程锁计数。计数变为零时释放写线程锁。如果线程持有读线程锁或没有锁,调用 ReleaseWriterLock 会引发 ApplicationException。
    • ReleaseLock():释放锁,不管线程获取锁的次数如何。

      差不多了吧,其它Member可以参见这里

    Sample Code

      是的,仍然偷懒了,因为仍然想不出好的有些实际意义的例子。MSDN在关于ReaderWriterLock类本身的介绍中,以及以上各方法的说明里都给出了若干Sample Code,请自行参考吧。



原文链接:http://www.blogbus.com/xxinside-logs/47780781.html

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C# 中,线程并发和线程同步是处理多线程编程中重要的概念。 线程并发指的是多个线程同时执行,这可能导致资源竞争和不确定的结果。为了避免这种情况,我们可以使用线程同步机制来确保线程之间的协调和有序执行。 C# 提供了多种线程同步的机制,下面是一些常用的方法: 1. 互斥锁(Mutex):互斥锁是一种排他锁,只允许一个线程访问被保护的资源。可以使用 `Mutex` 类来创建和管理互斥锁。 2. 信号量(Semaphore):信号量是一种计数器,用于控制同时访问某个资源的线程数。可以使用 `Semaphore` 类来创建和管理信号量。 3. 自旋锁(SpinLock):自旋锁是一种忙等待锁,线程会一直尝试获取锁,直到成功为止。可以使用 `SpinLock` 结构来创建和管理自旋锁。 4. 互斥量(Mutex):互斥量是一种特殊的信号量,只能被一个线程持有。可以使用 `Mutex` 类来创建和管理互斥量。 5. 事件(Event):事件是一种同步机制,在多个线程之间发送信号进行通信。可以使用 `ManualResetEvent` 或 `AutoResetEvent` 类来创建和管理事件。 除了上述方法外,还有一些其他的线程同步机制,如读写锁ReaderWriterLock)、条件变量(Monitor)等。选择适合场景的线程同步机制很重要,以确保线程安全和性能。 需要注意的是,线程并发和线程同步是一个复杂的主题,需要深入学习和实践才能掌握。在编写多线程代码时,建议仔细考虑并发问题,并使用适当的线程同步机制来确保代码的正确性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值