Single write program / multiple readers have been implemented in the .NET class library, that is, the System.Threading.ReaderWriterlock class. This article explores the C # multi-threaded programming by analysis of common single write / multiple readers.
statement of problem
The thread synchronization problem of a single write program / multiple readers means that when any number of threads access shared resources, the write program (thread) needs to modify the shared resource, and the reader (thread) needs to read the data. In this synchronous problem, it is easy to get the following two requirements:
1) When a thread is writing data, other threads cannot be written, and cannot be read.
2) When a thread is reading data, other threads cannot be written, but can be read.
This problem is often encountered in a database application environment. For example, there are N end users who have access to the same database at the same time. There are M users to store data into the database, and N-M users want to read records in the database.
Obviously, in this environment, we can't let two or more users update the same record at the same time. If two or more users tried to modify the same record simultaneously, the information in the record will be damage.
We also don't let the user update the database record while allowing another user to read the contents of the record. Because the read record is likely to include information that contains updates and not updated, that is, this record is an invalid record.
Implementation analysis
It is specified that any thread must apply for a lock before writing or reading the resource. Depending on the operation, it is divided into reading locks and write locks, and the corresponding lock should be released after the operation is completed. Change a single write program / multiple readers, you can get the following form:
A thread application reading lock is: there is currently no way to write threads.
A thread application write lock has no thread that currently does not have any activities (for the lock).
Therefore, in order to mark whether there is an active thread, and write a variable m_nactive, if m_nactive> 0, if m_nactive> 0, if m_nactive = 0, there is no activity thread, m_nactive <0, indicating that the current written thread is active, pay attention to M_NACTIVE <0, only -1 value is only available, because only one write thread activity is allowed.
In order to determine the type of lock owned by the current active thread, we use thread local storage techniques (see other reference books) to associate threads with special flags.
The function prototype of the application reading lock is: Public Void AcquireReaderLock (INT MilliseCondstimeout), where the parameters are the time of the thread waiting schedule. The function is defined as follows:
Public Void AcquireReaderlock (int MilliseCondstimeout)
{
// m_mutext can be obtained soon to enter the critical area
m_mutex.waitone ();
/ / Whether there is a write thread
Bool bexistingwriter = (m_nactive <0);
IF (BexistingWriter)
{// Waiting for the number of reading thread plus 1, when there is a lock release, according to this number to schedule the thread
M_nwaitingReaders ;
}
Else
{// Current active thread plus 1
m_nactive ;
}
m_mutex.releasemutemutex ();
// Storage lock flag is Reader
System.localdatastoreSlot slot = thread.getnamedDataSlot (m_strthreadslotname); Object obj = thread.getdata (slot);
Lockflags flag = LOCKFLAGS.NONE;
IF (Obj! = NULL)
Flag = (lockflags) OBJ;
IF (Flag == Lockflags.none)
{
Thread.SetData (slot, lockflags.reader);
}
Else
{
Thread.SetData (Slot, (INT) FLAG | (Int) Lockflags.Reader);
}
IF (BexistingWriter)
{// Waiting for the specified time
THIS.M_AEREADERS.WAITONE (MilliseCondstimeout, true);
}
}
It first entered the critical area (to ensure the correctness of the operation of the active thread in a multi-threaded environment) to determine the number of current active threads, if there is a write thread (m_nactive <0), wait for the specified time and wait for reading The number of thread plus 1. If the current active thread is a reading thread (m_nactive> = 0), you can continue to run.
The function prototype of the application is: public void acquirewriterlock (int MilliseCondstimeout), where the parameters are waiting for the scheduled time. The function is defined as follows:
Public void acquirewriterlock (int MilliseCondstimeout)
{
// m_mutext can be obtained soon to enter the critical area
m_mutex.waitone ();
// Is there an active thread
Bool bnoactive = m_nactive == 0;
IF (! bnoactive)
{
M_nwaitingWriters ;
}
Else
{
m_nactive--;
}
m_mutex.releasemutemutex ();
// Storage thread lock flag
System.localDataStoSlot slot = thread.getnamedDataSlot ("MyReaderWriterlockDataSlot");
Object obj = thread.getdata (slot);
Lockflags flag = LOCKFLAGS.NONE;
IF (Obj! = NULL)
Flag = (Lockflags) Thread.getdata (slot);
IF (Flag == Lockflags.none)
{
Thread.SetData (slot, lockflags.writer);
}
Else
{
Thread.SetData (Slot, (INT) FLAG | (Int) Lockflags.writer);
}
// If there is an active thread, wait for the specified time
IF (! bnoactive)
THIS.M_AEWRITERS.WAITONE (Millisecondstimeout, true);
}
It first enters the critical area to determine the number of current active threads. If there is an active thread exists, no matter whether the write thread is still a read thread (m_nactive), the thread will wait for the specified time and wait for the number of write threads plus 1, otherwise the thread has written permission.
The function prototype of the release of the reading lock is: public void releasereaderlock (). The function is defined as follows: Public void releasereaderlock ()
{
System.localDataStractoSlot slot = thread.getnameddataSlot (m_strthreadslotname);
Lockflags flag = (lockflags) thread.getdata (slot);
IF (Flag == Lockflags.none)
{
Return;
}
Bool breader = true;
Switch (flag)
{
Case LockFlags.none:
Break;
Case LockFlags.writer:
Breader = false;
Break;
}
IF (!!)
Return;
Thread.SetData (slot, lockflags.none);
m_mutex.waitone ();
AutoreteEvent AutoreseVent = NULL;
THIS.M_NACTIVE -;
IF (this.m_nactive == 0)
{
IF (this.m_nwaitingReaders> 0)
{
m_nactive ;
M_nwaitingReaders -;
AutoreteEvent = this.m_aereaders;
}
Else IF (this.m_nwaitingWriters> 0)
{
M_nwaitingWriters--;
m_nactive -;
AutoreteEvent = this.m_aewriters;
}
}
m_mutex.releasemutemutex ();
IF (AutoreteEvent! = NULL)
AutoreteEvent.Set ();
}
When you release the reading lock, first determine if the current thread has a reading lock (the logo stored by the thread), then determines if there is a waiting thread, if there is, first add the current active thread plus 1, wait for the number of ways to reduce 1, then The event is a signal. If there is no waiting for reading thread, it is determined whether there is a waiting write thread. If there is a number of active threads minus 1, the number of waits will be reduced by 1. Release the write lock and the process of release reading locks is basically consistent, you can see the source code.
Note that when the lock is released, you will only wake up a reading program because the reader can change itself to ManualResetEvent, and the M_NACTIVE is equal to M_NACTIVE. Read the number of threads.
test
The test program is taken from an example in .NET Framesdk, just make a modification. The test procedure is as follows,
Using system;
Using system.threading;
Using mythreading;
Class resource
{
MyReaderwriterlock rwl = new myreaderwriterlock ();
Public void read (INT32 Threadnum)
{
RWL.AcquireReaderlock (Timeout.infinite);
Try
{
Console.writeline ("Start Resource Reading (THREAD = {0})", Threadnum);
Thread.sleep (250);
Console.writeline ("Stop Resource Reading (THREAD = {0})", Threadnum);
Finally
{
Rwl.releaseReaderlock ();
}
}
Public Void Write (int32 threadnum)
{
RWL.AcquireWriterlock (Timeout.Infinite);
Try
{
Console.writeline ("Start Resource Writing (Thread = {0})", Threadnum);
Thread.sleep (750);
Console.writeline ("stop resource Writing (thread = {0})", threadnum);
}
Finally
{
Rwl.releaseWriterlock ();
}
}
}
Class app
{
Static int32 NumasyncOPS = 20;
Static AutoreteTevent Asyncopsaredone = New AutoreteEvent (False);
Static resource res = new resource ();
Public static void main ()
{
For (int32 threadnum = 0; threadnum <20; threadnum )
{
ThreadPool.QueueUserWorkItem (New Waitcallback (Updateresource), Threadnum
}
AsyncopsaredOadone.waitone ();
Console.writeline ("All Operations Have completed.");
Console.readline ();
}
// The callback method's signature must match what of a system.threading.timerCallback
// delegate (it takes an object parameter and return void)
Static void Updateresource (Object State)
{
INT32 threadnum = (int32) state;
IF ((Threadnum% 2)! = 0) res.read (threadnum);
Else Res.Write (threadnum);
IF (Interlocked.Decrement (Ref NumasyncOps) == 0)
AsyncopsaredOadone.set ();
}
}
As can be seen from the test results, you can meet the implementation requirements of a single write program / multiple readers.