Hello everyone,
welcome back to can you spot the deadlock take 4. As a reminder, this a puzzle in a form of multithreaded code. You have to identify the issue that hides in it, usually a deadlock, but not always. Please post your answer in the comments section.
Since it has been a long time since the last episode, let’s start easy!
So what is the problem with this code and how would you fix it?
class Program { static void Main(string[] args) { var workerThread = new Thread(Worker); workerThread.Start(); workerThread.Join(); } static void Worker() { while (Thread.CurrentThread.IsAlive) { Console.Write("Blah "); } } }
Can You Spot the Deadlock 3?
Hello dear readers and coders
Welcome to the third ‘Can You Spot the Deadlock?’ trivia. Today’s exercise is still dedicated to the Monitor Pattern. Next one will explore other horizons.
As usual the code is in C#, it does compile but it will not run as is.
So:
- What is the issue here?
- Where does it occur?
- Can you fix it ? if yes how, if not, why
And please share your ideas through the comment!
using System; using System.Threading; namespace SpotTheDeadLock { internal class SpotTheDeadlock3 { readonly object _synchro = new object(); // lock object public int _x = 1; // resulting data bool _mustStop; int _iteration = 0; int []_factor={2,3,5}; // first running thread public void Runner1(int stepId) { while (true) { lock (_synchro) { if (_iteration % 3 != stepId && !_mustStop) Monitor.Wait(_synchro); if (_mustStop) return; _x = _x * _factor[stepId]; NextStep(); } // _synchro } // while } private void NextStep() { if (_iteration == 8) _mustStop = true; else _iteration++; Monitor.PulseAll(_synchro); } public static void Runner() { SpotTheDeadlock3 agent = new SpotTheDeadlock3(); Thread thread1 = new Thread(() => agent.Runner1(0)); Thread thread2 = new Thread(() => agent.Runner1(1)); Thread thread3 = new Thread(() => agent.Runner1(2)); thread1.Start(); thread2.Start(); thread3.Start(); thread1.Join(); thread2.Join(); thread3.Join(); if (agent._x != 2 * 2 * 2 * 3 * 3 * 3 * 5 * 5 * 5) throw new ApplicationException("Invalid result"); } } // class } // namespace
Quizz time is closed, solution is available here
CYSDL 2: call to arms
Hello, nobody tried to answer my last Can You Spot the Deadlock?. You can still be the first to find the right answer. The solution will be releaved next Monday (Feb 25th)
Can You Spot the Deadlock 2?
Hello dear readers and coders
Welcome to the second ‘Can You Spot the Deadlock?’ trivia. I surely hope you had fun with the first one. Yeah, I know, it was kinda easy. So today, I raise the bar a bit and bring you one of my favorites: the transactional subscription/un subscription pattern.
The code compiles but it will not run, as I did not show the scaffolding code. Suffice to say that there is a notification mechanism that lives in its own thread(s) and some client code logic that rely on the main(starting) thread.
So:
- What is the issue here?
- Where does it occur?
- Can you fix it ? if yes how, if now, why
Problem is now closed, please find the solution
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SpotTheDeadLock2 { /// /// Implements aynchronous notification /// public class EventSource { private EventHandler _eventHandler; /// /// notification event /// public event EventHandler EventOccured { add { // lock to secure subscription lock (this){ _eventHandler += value; } } remove { // lock to secure unsubscription lock (this) { _eventHandler -= value; } } } // implementation of notification private void TriggerEvent() { lock (this) { if (_eventHandler != null) { _eventHandler(this, new EventArgs()); } } } } /// /// client implementation /// public class Client: IDisposable { private EventSource _source; /// /// Simple constructor /// ///data source to subsribe to public Client(EventSource source) { _source = source; _source.EventOccured += OnEvent; } /// /// Method in charge of processing events /// private void OnEvent(object sender, EventArgs arguments) { // use lock as we do not have control of calling thread lock (this) { // do my job ... } } // unsubscribe on clean up public void Dispose() { // use lock to ensure no processing is in progress lock (this) { _source.EventOccured -= OnEvent; } } } }
CYSDL 1: Solution
As promised, here is the answer to my question
Here the deadlock manifests itself as the main thread stuck on one of the Thread.Join calls and the associated thread’s (_firstRunner or _secondRunner) Monitor.Wait(_synchro) call.
The problem lies with the Monitor.Pulse(_synchro) call, because it wakes only one thread waiting allowing it to end properly. The other thread is still waiting for a signal that will never happen. And ultimately, the main thread is waiting for both threads to end, and we already know that it will not happen.
How to fix: you need to replace Monitor.Pulse(_synchro) by Monitor.PulseAll(_synchro).
This example reminds us that deadlocks do not systematicaly occurs on lock statement and that one must be careful regarding thread termination. Plus, here the problem does not relate to the classical ordering of resource acquisition.
The only random aspect was which of the two runner threads would be locked.
It also raises concern about when and why someone has to worry about using Pulse or PulseAll.
How can it be improved: there is a call to Thread.Sleep that I did place just to make sure that both runner thread where waiting for the signal. This is never the proper way to make a rendez-vous point between threads. But this is neither a trivial matter and adequate rendez-vous code would add a significant chunck of code.
Rendez-vous will probably a topic for another exercise.
How did you fare in this exercise
PS: This an automated post