Recipe 15.8 Synchronizing the Reading and Writingof a Resource Efficiently
Problem
You have a
resource that is shared by multiple threads. You need to provide
exclusive access to this resource when a thread is writing to it.
However, you do not want the overhead of providing exclusive access
to this resource when multiple threads are only reading from it. You
want to allow one thread to access a shared resource only if it is
writing to it, but you also want to allow multiple threads to read
from this resource. While multiple threads can read from a resource,
a write operation cannot occur while any thread is reading from this
resource.
Solution
Use
the ReaderWriterLock class from the FCL. The
ReaderWriterLock is optimized for scenarios where
you have data that changes infrequently but needs protection for
those times when it is updated in a multithreading scenario. To
illustrate, the GradeBoard class represents a
board where an instructor will post the grades students received from
a class. Many students can read the grade board, but only the
instructor can post a grade (write) to the grade board. Students will
not, however, be able to read from the board while the instructor is
updating it:
class GradeBoard
{
// make a static ReaderWriterLock to allow all student threads to check
// grades and the instructor thread to post grades
static ReaderWriterLock readerWriter = new ReaderWriterLock( );
// the grade to be posted
static char studentsGrade = ' ';
static void Main( )
{
// create students
Thread[] students = new Thread[5];
for(int i=0;i<students.Length;i++)
{
students[i] = new Thread(new ThreadStart(StudentThreadProc));
students[i].Name = "Student " + i.ToString( );
// start the student looking for a grade
students[i].Start( );
}
// make those students "wait" for their grades by pausing the instructor
Thread.Sleep(5000);
// create instructor to post grade
Thread instructor = new Thread(new ThreadStart(InstructorThreadProc));
instructor.Name = "Instructor";
// start instructor
instructor.Start( );
// wait for instructor to finish
instructor.Join( );
// wait for students to get grades
for(int i=0;i<students.Length;i++)
{
students[i].Join( );
}
}
static char ReadGrade( )
{
// wait ten seconds for the read lock
readerWriter.AcquireReaderLock(10000);
try
{
// now we can read safely
return studentsGrade;
}
finally
{
// Ensure that the lock is released.
readerWriter.ReleaseReaderLock( );
}
}
static void PostGrade(char grade)
{
// wait ten seconds for the write lock
readerWriter.AcquireWriterLock(10000);
try
{
// now we can post the grade safely
studentsGrade = grade;
Console.WriteLine("Posting Grade...");
}
finally
{
// Ensure that the lock is released.
readerWriter.ReleaseWriterLock( );
}
}
static void StudentThreadProc( )
{
bool isGradeFound = false;
char grade = ' ';
while(!isGradeFound)
{
grade = ReadGrade( );
if(grade != ' ')
{
isGradeFound = true;
Console.WriteLine("Student Found Grade...");
}
else // check back later
Thread.Sleep(1000);
}
}
static void InstructorThreadProc( )
{
// everyone likes an easy grader :)
PostGrade('A');
}
}
Discussion
In the
example, the ReaderWriterLock protects access to
the grade resource of the GradeBoard class. Lots
of students can be continually reading their grades using the
ReadGrade method, but once the instructor attempts
to post the grades using the PostGrade method, the
grade resource is locked so that no one but the instructor can access
it. The instructor updates the grades and releases the lock, and the
pending student read requests are allowed to resume. All students
continue to read the grade board, check to see if the grades have
been posted, and then wait before making another request. Once the
grades are posted, each student finds it, and the thread for that
student terminates.
The Main method calls
Join on the instructor and student threads to wait
until those threads finish before continuing and ending. If it did
not do this, the program could potentially end before the threads
finish. It protects against a
ThreadInterruptedException, as the Join calls
could potentially throw this if the thread aborts. The threads are
named using the Name property to ease debugging.
See Also
See the "ReaderWriterLock Class"
and "Thread Class" topics in the
MSDN documentation.
|