Concurrent Data Structure: read/write locks provided in. NET Framework

Source: Internet
Author: User
ArticleDirectory
    • In multi-threaded programming, developers often encounter multiple threads to read and write a resource. This requires thread synchronization to ensure thread security. In general, our synchronization method is to use the lock mechanism. However, if the thread only reads resources, there is no need to use the lock. Otherwise, if the thread only writes resources, the mutex lock should be used (for example, the monitor class ). There is also a situation where multiple threads read resources and only one thread exclusively writes the resources at a time. This is the topic of this article-the use of read/write locks.
In multi-threaded programming, developers often encounter multiple threads to read and write a resource. This requires thread synchronization to ensure thread security. In general, our synchronization method is to use the lock mechanism. However, if the thread only reads resources, there is no need to use the lock. Otherwise, if the thread only writes resources, the mutex lock should be used (for example, the monitor class ). There is also a situation where multiple threads read resources and only one thread exclusively writes the resources at a time. This is the topic of this article-the use of read/write locks. Readerwriterlock class

In Version 1.1, the. NET Framework Bcl provides a readerwriterlock class for this scenario. Unfortunately, Microsoft does not recommend this type. Jeffrey Richter also severely criticized it in his book CLR via C. The following are the main reasons for this type of popularity:

    • performance. This class is too slow. For example, its acquirereaderlock method is about five times slower than the enter method of the monitor class, while waiting for a write lock is about six times slower than the monitor class.
    • Policy. If a thread completes the write operation, both the read thread and the write thread are waiting for processing. Readerwriterlock will first release the read thread and let the write thread continue to wait. But we use the read/write lock because there are a large number of read threads and a very small number of write threads. In this way, the write thread may have to wait for a long time, resulting in hunger of the write thread and failure to update data in time. Even worse, if the write thread waits for a long time, it will cause a live lock. Instead, let readerwriterlock take the write thread priority policy. If multiple write threads exist and the number of read threads is scarce, the read threads may become hungry. Fortunately, in practice, this rarely happens. In this case, we can use a mutex lock.
    • recursion. The readerwriterlock class supports lock recursion. This means that the lock knows exactly which thread owns it. If the thread that owns the lock attempts to obtain the read/write lock recursively, the recursive algorithm allows the thread to obtain the read/write lock, and increase the number of locks obtained. However, the thread must release the lock for the same number of times so that the thread no longer has the lock. Although this seems to be a good feature, it is too costly to implement this "feature. First, because multiple read threads can have the read/write lock at the same time, this must keep the lock count for each thread. In addition, additional memory space and time are required to update the count. This feature greatly contributes to the poor performance of the readerwriterlock class. Secondly, some good designs require a thread to obtain the lock here, and then release the lock elsewhere (such as. Net's asynchronous programming architecture ). Because of this recursion feature, readerwriterlock does not support this programming architecture.
    • resource leakage. In versions earlier than. NET 2.0, the readerwriterlock class may cause Kernel Object leakage. These objects can be recycled only after the process is terminated. Fortunately,. NET 2.0 corrected this bug.

In addition, readerwriterlock has a worrying and dangerous non-atomic operation. It is the upgradetowritelock method. This method actually releases the read lock before it is updated to the write lock. This gives other threads the opportunity to gain a read/write lock and change the status during this period. If the write lock is updated first, the read lock is released. If two threads are updated at the same time, another thread will be deadlocked.

Microsoft decided to build a new class to solve all the above problems at one time, which is the readerwriterlockslim class. The original readerwriterlock class can be used to correct errors, but Microsoft gave up this practice considering compatibility and existing APIs. Of course, you can also mark the readerwriterlock class as obsolete, but for some reason, this class is still necessary.

Readerwriterlockslim class

The new readerwriterlockslim class supports three lock modes: read, write, and upgradeableread. The three modes correspond to enterreadlock, enterwritelock, and enterupgradeablereadlock. The corresponding tryenterreadlock, tryenterwritelock, tryenterupgradeablereadlock, exitreadlock, exitwritelock, exitupgradeablereadlock. The read and writer lock modes are relatively simple and easy to understand: the Read mode is a typical shared lock mode. Any number of threads can get the lock in this mode at the same time; the writer mode is the mutex mode, in this mode, only one thread is allowed to enter the lock. The upgradeableread locking mode may be new to most people, but it is well known in the database field.

The performance of this new read/write lock class is roughly the same as that of the monitor class, which is roughly twice that of the monitor class. In addition, the new lock gives priority to the write thread to obtain the lock because the write operation frequency is much lower than the read operation frequency. This usually leads to better scalability. At first, the readerwriterlockslim class was designed to take into account a considerable number of situations. For exampleCodeIt also provides competitive strategies such as prefersreaders, preferswritersandupgrades, and FIFO. However, although these policies are simple to add, they may cause complicated situations. Microsoft finally decided to provide a simple model that can work well in most cases.

Readerwriterlockslim update lock

Now let's discuss how to update the model in depth. The upgradeableread locking mode allows secure updates in read or write mode. Do you still remember the previous update of readerwriterlock is non-atomic and dangerous (especially when most people are not aware of this )? The new read/write locks provided now do not disrupt atomicity or lead to deadlocks. The new lock allows only one thread to be in upgradeableread mode at a time.

Once the read/write lock is in upgradeableread mode, the thread can read certain status values to determine whether to downgrade to read mode or upgrade to write mode. Note that you should make this decision as quickly as possible: holding the upgradeableread lock will force any new read requests to wait, although the existing read operations are still active. Unfortunately, the CLR team removed the downgradetoread and upgradetowrite methods. If you want to downgrade to the read lock, simply call the enterreadlock method after the exitupgradeablereadlock method: This allows other read and upgradeableread to complete the operations that should be held but held by the upgradeableread lock. To upgrade to the write lock, simply call the enterwritelock method: this may have to wait until no threads hold the lock in Read mode. Unlike downgrading to read locks, you must call exitupgradeablereadlock. In write mode, you do not have to call exitupgradeablereadlock. However, to unify the form, it is best to call it. For example, the following code:

 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 ( /*... Read status value to determine whether to update... */ ) {Rwlock. enterwritelock (); Try { //... Write status value... } Finally {Rwlock. exitwritelock ();}} Else {Rwlock. enterreadlock (); rwlock. exitupgradeablereadlock (); isupdated = False ; Try { //... Read status value... } Finally {Rwlock. exitreadlock ();}}} Finally { If (Isupdated) rwlock. exitupgradeablereadlock ();}}}}
Readerwriterlockslim recursion Policy

Another interesting feature of the new read/write lock is its recursive strategy. By default, all recursive requests are not allowed except the mentioned downgrade to read lock and write lock. This means that you cannot call enterreadlock twice in a row. It is similar in other modes. If you do this, the CLR will throw a lockrecursionexception. Of course, you can use the lockrecursionpolicy. supportsrecursion constructor parameter to allow the read/write lock to support recursive locking. But we do not recommend that you use recursion for new development, because recursion will lead to unnecessary complexity, making your code more prone to deadlocks.

There is a special situation that is never allowed, no matter what recursive strategy you adopt. This is the write lock requested when the thread holds the read lock. Microsoft once considered providing such support, but this situation is too easy to cause deadlocks. So Microsoft finally gave up the solution.

In addition, this new read/write lock provides many corresponding attributes to determine whether the thread holds the lock under the specified model. For example, isreadlockheld, iswritelockheld, and isupgradeablereadlockheld. You can also use attributes such as waitingreadcount, waitingwritecount, and waitingupgradecount to check how many threads are waiting to hold the lock in a specific mode. The currentreadcount attribute indicates the number of concurrent read threads currently. Recursivereadcount, recursivewritecount, and recursiveupgradecount indicate the number of times the thread is locked in a specific mode.

Summary

This article analyzes the two read/write lock classes provided by. net. However, the readerwriterlockslim class provided by. Net 3.5 eliminates the main problems of the readerwriterlock class. Compared with readerwriterlock, the performance is greatly improved. Updates are atomic and can avoid deadlocks. More Clear recursive strategies. In any case, we should use readerwriterlockslim to replace the readerwriterlock class.

Update

In Windows Vista and later versions, A srwlock primitive is added. It is built based on the Windows Kernel event mechanism. Its design is interesting.

The SRW lock does not support recursion. The Windows Kernel team believes that supporting recursion will cause additional system overhead because a thread-by-thread count is required to maintain accuracy. The SRW lock also does not support upgrading from shared access to exclusive access, and does not support downgrading from exclusive access to shared access. Upgrading capabilities may cause unacceptable complexity and additional system overhead. This overhead may even affect the common situation of intra-lock sharing and exclusive code acquisition. It also requires defining policies on how to select a reader in the waiting state, a writer in the waiting state, and a reader waiting for upgrade, which will conflict with the basic design objectives without bias. I encapsulated it in. net. The Code is as follows:

 Using System; Using System. Threading; Using System. runtime. interopservices; Namespace Lucifer. Threading. Lock { /// <Summary> ///  Read/write locks supported by Windows NT 6.0.  /// </Summary> /// <remarks>  Note that this class can only be used in NT 6.0 and later versions.  </Remarks> Public sealed class  Srwlock { Private  Intptr Rwlock; /// <Summary> ///  The lock does not support recursion.  /// </Summary>  Public Srwlock () {initializesrwlock ( Out Rwlock );} /// <Summary> ///  Obtain the read lock.  /// </Summary>  Public void Enterreadlock () {acquiresrwlockshared ( Ref Rwlock );} /// <Summary> ///  Obtain the write lock.  /// </Summary>  Public void Enterwritelock () {acquiresrwlockexclusive ( Ref Rwlock );} /// <Summary> ///  Release the read lock.  /// </Summary>  Public void Exitreadlock () {releasesrwlockshared ( Ref Rwlock );} /// <Summary> ///  Release the write lock.  /// </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 );}}

In addition, there are some interesting read/write locks on other platforms. For example, the read/write lock in the Linux kernel and the read/write lock in Java. You can study it on your own.

Read/write locks are commonly used in cache design. Because the cached content is often quite stable and not very long updated. The msdn sample code is very classic. Copy the original version. The sample code is as follows:

 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 };}}
Update again

If the application scenario requires high performance, consider using the lock-free solution. But lock-free has its inherent defect: it is extremely difficult to code and prove its correctness. The read/write Lock solution is more widely used.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.