Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   The Condition Variable Pattern

Let’s say that a thread wants to execute some code when a complex condition is true. One option would be to let the thread spin continuously, repeatedly testing the condition. But this wastes CPU time, and it is also not possible to atomically test multiple variables that are making up the complex condition. Fortunately, there is a pattern that allows threads to efficiently synchronize their operations based on a complex condition.


This pattern is called the condition variable pattern, and we use it via the following methods de-

fined inside the Monitor class.

 

public static class Monitor {

public static Boolean Wait(Object obj);

public static Boolean Wait(Object obj, Int32 millisecondsTimeout);

 

public static void Pulse(Object obj); public static void PulseAll(Object obj);

}

 

Here is what the pattern looks like.

 

internal sealed class ConditionVariablePattern { private readonly Object m_lock = new Object(); private Boolean m_condition = false;

 

public void Thread1() {

Monitor.Enter(m_lock); // Acquire a mutual­exclusive lock

 

// While under the lock, test the complex condition "atomically" while (!m_condition) {

// If condition is not met, wait for another thread to change the condition Monitor.Wait(m_lock); // Temporarily release lock so other threads can get it

}

 

// The condition was met, process the data...

 

Monitor.Exit(m_lock); // Permanently release lock

}

 

public void Thread2() {

Monitor.Enter(m_lock); // Acquire a mutual­exclusive lock

 

// Process data and modify the condition... m_condition = true;

 

// Monitor.Pulse(m_lock); // Wakes one waiter AFTER lock is released Monitor.PulseAll(m_lock); // Wakes all waiters AFTER lock is released

 

Monitor.Exit(m_lock); // Release lock

}

}

 

In this code, the thread executing the Thread1 method enters a mutual-exclusive lock and then tests a condition. Here, I am just checking a Boolean field, but this condition can be arbitrarily com- plex. For example, you could check to see if it is a Tuesday in March and if a certain collection object has 10 elements in it. If the condition is false, then you want the thread to spin on the condition, but spinning wastes CPU time, so instead, the thread calls Wait. Wait releases the lock so that another thread can get it and blocks the calling thread.


The Thread2 method shows code that the second thread executes. It calls Enter to take owner- ship of the lock, processes some data, which results in changing the state of the condition, and then calls Pulse or PulseAll, which will unblock a thread from its Wait call. Pulse unblocks the longest waiting thread (if any), whereas PulseAll unblocks all waiting threads (if any). However, any un- blocked threads don’t wake up yet. The thread executing Thread2 must call Monitor.Exit, allowing the lock to be owned by another thread. Also, if PulseAll is called, the other threads do not unblock simultaneously. When a thread that called Wait is unblocked, it becomes the owner of the lock, and because it is a mutual-exclusive lock, only one thread at a time can own it. Other threads can get it after an owning thread calls Wait or Exit.



When the thread executing Thread1 wakes, it loops around and tests the condition again. If the condition is still false, then it calls Wait again. If the condition is true, then it processes the data as it likes and ultimately calls Exit, leaving the lock so other threads can get it. The nice thing about this pattern is that it is possible to test several variables making up a complex condition using simple syn- chronization logic ( just one lock), and multiple waiting threads can all unblock without causing any logic failure, although the unblocking threads might waste some CPU time.

Here is an example of a thread-safe queue that can have multiple threads enqueuing and de- queuing items to it. Note that threads attempting to dequeue an item block until an item is available for them to process.

 

internal sealed class SynchronizedQueue<T> { private readonly Object m_lock = new Object();

private readonly Queue<T> m_queue = new Queue<T>();

 

public void Enqueue(T item) { Monitor.Enter(m_lock);

 

// After enqueuing an item, wake up any/all waiters m_queue.Enqueue(item);

Monitor.PulseAll(m_lock);

 

Monitor.Exit(m_lock);

}

 

public T Dequeue() { Monitor.Enter(m_lock);

 

// Loop while the queue is empty (the condition) while (m_queue.Count == 0)

Monitor.Wait(m_lock);

 

// Dequeue an item from the queue and return it for processing T item = m_queue.Dequeue();

Monitor.Exit(m_lock); return item;

}

}



Date: 2016-03-03; view: 715


<== previous page | next page ==>
Nbsp;   The Famous Double-Check Locking Technique | Nbsp;   Asynchronous Synchronization
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.009 sec.)