In the print dialog, choose "Save as PDF" as the destination.
Operating Systems 1, Lab 6
When threads share data: lost updates, lock, Monitor, and the UI thread
locklock compiles to: Monitor.Enter, Monitor.Exit, try/finallyMonitor: TryEnter with a timeout, and Wait/Pulse to make threads take turnsSemaphoreInvoke or a BackgroundWorkerPart 1
Same code, different answers
Three threads start SomeMethod; Main joins them and prints the time. Predict the output.
static void SomeMethod()
{
Console.Write("[Welcome To The ");
Thread.Sleep(1000);
Console.WriteLine("World of Dotnet!]");
}[Welcome To The [Welcome To The [Welcome To The World of Dotnet!]
World of Dotnet!]
World of Dotnet!]
Total: 1.1 sT1 books 3, T2 books 2. Each thread reads the count, works for 100 ms, then writes the new count. Predict Tickets left.
static int availableTickets = 10;
static void Book(int wanted)
{
string name = Thread.CurrentThread.Name;
int seen = availableTickets; // 1) read
Console.WriteLine(name + " read " + seen);
Thread.Sleep(100); // a slow step
availableTickets = seen - wanted; // 2) compute, 3) write
Console.WriteLine(name + " wrote " + availableTickets);
}T1 read 10
T2 read 10
T1 wrote 7
T2 wrote 8
Tickets left: 8count++ Is Three StepsThe IL the compiler produced for count++ in our counter program (read with a disassembler)
count++;ldsfld int32 Program::count // 1) read count
ldc.i4.1
add // 2) add 1
stsfld int32 Program::count // 3) write count5 both write 6: one increment is lostEach thread runs count++ 1,000,000 times. Called one after another, the program prints count = 3000000. Predict the threaded result.
static int count = 0;
static void IncrementCount()
{
for (int i = 1; i <= 1000000; i++)
{
count++;
}
}
// Main: start t1, t2, t3 on IncrementCount,
// Join all three, print count| Run | count |
|---|---|
| 1 | 2433729 |
| 2 | 1646435 |
| 3 | 1076791 |
| 4 | 1508029 |
| 5 | 1179164 |
Thread1 wants 1, Thread2 wants 2, Thread3 wants 3, all on one BookMyShow object. Predict: how many tickets get booked?
public int AvailableTickets = 3;
public void BookTicket(string name, int wantedTickets)
{
if (wantedTickets <= AvailableTickets)
{
Console.WriteLine(wantedTickets + " booked to " + name);
AvailableTickets = AvailableTickets - wantedTickets;
}
else
{
Console.WriteLine("No tickets available to book for " + name);
}
}2 booked to Thread2
1 booked to Thread1
3 booked to Thread3
Tickets left: -3if that checks AvailableTickets and the line that updates itcount++, all three of its steps| Goal | Meaning | Example |
|---|---|---|
| Atomicity | the critical section runs as one unit, by one thread at a time | ticket booking, the counter |
| Ordering | threads work in a chosen order, or only a limited number may enter | one whole message at a time; even and odd numbers in turn |
Part 2
One thread in the critical section at a time
private static readonly object lockObject = new object();
static void SomeMethod()
{
lock (lockObject)
{
// critical section:
// one thread at a time
}
}lock acquires the lock and enterslock until the owner leaves the blockprivate: outside code cannot lock it; readonly: it can never be replacedstatic method: static lock object; an instance method may use an instance fieldSame three threads; the body of SomeMethod is now inside lock, with a message before, inside and after. Predict the order and the time.
Console.WriteLine(name + " trying to acquire the lock");
lock (lockObject)
{
Console.WriteLine(name + " got the lock");
Console.Write("[Welcome To The ");
Thread.Sleep(1000);
Console.WriteLine("World of Dotnet!]");
Console.WriteLine(name + " releases the lock");
}Thread 2 trying to acquire the lock
Thread 3 trying to acquire the lock
Thread 3 got the lock
[Welcome To The Thread 1 trying to acquire the lock
World of Dotnet!]
Thread 3 releases the lock
Thread 2 got the lock
[Welcome To The World of Dotnet!]
Thread 2 releases the lock
Thread 1 got the lock
[Welcome To The World of Dotnet!]
Thread 1 releases the lock
Total: 3.0 sPut only count++ inside the lock. Predict count.
private static readonly object lockCount = new object();
static void IncrementCount()
{
for (int i = 1; i <= 1000000; i++)
{
lock (lockCount)
{
count++;
}
}
}count = 30000003000000i is a local variable: each thread has its own, so the loop stays outside the lock3 x 1000000 times: correct, but slower than no lockWrap the check and the update in one lock. Predict the possible outputs.
lock (lockObject)
{
if (wantedTickets <= AvailableTickets)
{
Console.WriteLine(wantedTickets + " booked to " + name);
AvailableTickets = AvailableTickets - wantedTickets;
}
else
{
Console.WriteLine("No tickets available to book for " + name);
}
}| Runs | Output, 60 runs |
|---|---|
| 51 | 1 to Thread1, 2 to Thread2, none for Thread3 |
| 7 | 1 to Thread1, none for Thread3, 2 to Thread2 |
| 1 | 2 to Thread2, 1 to Thread1, none for Thread3 |
| 1 | 2 to Thread2, none for Thread3, 1 to Thread1 |
The compiler turns lock (lockObject) { body } into this
object temp = lockObject;
bool lockTaken = false;
try
{
Monitor.Enter(temp, ref lockTaken);
// body
}
finally
{
if (lockTaken)
{
Monitor.Exit(temp);
}
}Monitor.Enter gives the lock to one thread and sets that thread's lockTaken to trueref: Enter writes into the caller's own variablefinally always runs, even after an exception, so the lock is always releasedcount = 3000000 in 10 of 10 runsThe IL of IncrementCount in the compiled lock version, trimmed. bool& means ref bool.
.try
{
call void System.Threading.Monitor::Enter(object, bool&)
ldsfld int32 Program::count
ldc.i4.1
add
stsfld int32 Program::count
leave.s IL_0032
}finally
{
ldloc.2 // lockTaken
brfalse.s IL_0031 // false: skip Exit
call void System.Threading.Monitor::Exit(object)
}Thread A throws inside the lock and catches it outside. Then B takes the same lock; Main gives it 2 s: b.Join(TimeSpan.FromSeconds(2)) returns true if B finished. Predict the last line.
static void Faulty() // thread A
{
try
{
lock (lockObject)
{
Console.WriteLine("A has the lock");
int tickets = int.Parse("three"); // throws FormatException
}
} catch (FormatException ex)
{
Console.WriteLine("A caught: " + ex.Message);
}
}A has the lock
A caught: The input string 'three' was not in a correct format.
B has the lock
B finished within 2 s: TrueSame program, but thread A uses Monitor.Enter and Monitor.Exit with no finally. Predict the last line.
try
{
Monitor.Enter(lockObject);
Console.WriteLine("A has the lock");
int tickets = int.Parse("three"); // throws FormatException
Monitor.Exit(lockObject); // skipped by the exception
}
catch (FormatException ex)
{
Console.WriteLine("A caught: " + ex.Message);
}A has the lock
A caught: The input string 'three' was not in a correct format.
B finished within 2 s: FalsePart 3
Everything lock does, plus timeouts and signals
| Method | What it does |
|---|---|
Enter(obj), Enter(obj, ref taken) | take the lock; wait in the ready queue while another thread holds it |
Exit(obj) | release the lock; a thread from the ready queue can take it |
TryEnter(obj, timeout, ref taken) | like Enter, but give up after the timeout (taken stays false) |
Wait(obj) | release the lock and sleep in the waiting queue until a Pulse |
Pulse(obj) | move one thread from the waiting queue to the ready queue |
Three threads run PrintNumbers. Predict: can two threads print their numbers at the same time?
Console.WriteLine(name + " trying to enter the critical section");
try
{
Monitor.Enter(lockPrintNumbers);
Console.WriteLine(name + " entered the critical section");
for (int i = 0; i < 5; i++)
{
Thread.Sleep(100);
Console.Write(i + ",");
}
Console.WriteLine();
}finally
{
Monitor.Exit(lockPrintNumbers);
Console.WriteLine(name + " exit from the critical section");
}Child Thread 2 trying to enter the critical section
Child Thread 1 trying to enter the critical section
Child Thread 2 entered the critical section
Child Thread 3 trying to enter the critical section
0,1,2,3,4,
Child Thread 2 exit from the critical section
Child Thread 1 entered the critical section
0,1,2,3,4,
Child Thread 1 exit from the critical section
Child Thread 3 entered the critical section
0,1,2,3,4,
Child Thread 3 exit from the critical sectionEach thread holds the lock for 5 x 100 ms and waits at most 1 s for it. Predict: do all three threads print?
TimeSpan timeout = TimeSpan.FromSeconds(1);
bool lockTaken = false;
try
{
Monitor.TryEnter(lockPrintNumbers, timeout, ref lockTaken);
if (lockTaken)
{
// "entered", the 5 numbers, a new line: as before
}
else
Console.WriteLine(name + ": lock was not acquired");
}finally
{
if (lockTaken)
{
Monitor.Exit(lockPrintNumbers); // then prints "exit"
}
}Child Thread 2 trying to enter the critical section
Child Thread 3 trying to enter the critical section
Child Thread 3 entered the critical section
Child Thread 1 trying to enter the critical section
0,1,2,3,4,
Child Thread 3 exit from the critical section
Child Thread 2 entered the critical section
0,1,2,3,Child Thread 1: lock was not acquired
4,
Child Thread 2 exit from the critical sectionGoal: 0 1 2 3 ... 20, the even numbers from one thread and the odd numbers from another
0, then calls Pulse: a waiting thread may get readyWait: it releases the lock and sleeps in the waiting queue1, calls Pulse (evenThread gets ready), then WaitWait and prints 2, and so onWait: nobody would be left to pulse itnumberLimit = 20. Predict the output.
static void PrintEvenNumbers()
{
try
{
Monitor.Enter(lockMonitor); for (int i = 0; i <= numberLimit; i = i + 2)
{
Console.Write(i + " ");
Monitor.Pulse(lockMonitor);
bool isLast = (i == numberLimit);
if (!isLast)
Monitor.Wait(lockMonitor);
}
}
finally { Monitor.Exit(lockMonitor); }
}PrintOddNumbers: the same with i = 1 and isLast = (i == numberLimit - 1)lockMonitorMain: start evenThread, Thread.Sleep(100) so it runs first, start oddThread, Join both0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Main method completedBoth threads always call Wait. They are background threads, and Main waits at most 2 s for each: Join(TimeSpan) returns true if the thread finished. Predict the last line.
Console.Write(i + " ");
Monitor.Pulse(lockMonitor);
Monitor.Wait(lockMonitor); // no isLast check
// in Main
bool oddDone = oddThread.Join(TimeSpan.FromSeconds(2));
bool evenDone = evenThread.Join(TimeSpan.FromSeconds(2));
Console.WriteLine("odd finished: " + oddDone + ", even finished: " + evenDone);0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
odd finished: True, even finished: Falselock | Monitor | |
|---|---|---|
| Take and release | automatic, at { and } | Enter and Exit, by hand |
| After an exception | always released | only with your own try/finally |
| Give up after a timeout | no | TryEnter |
| Signal another thread | no | Wait, Pulse, PulseAll |
| Lock type | Who may enter | C# tools |
|---|---|---|
| Exclusive | one thread at a time | lock, Monitor, Mutex, SpinLock |
| Non-exclusive | a limited number of threads | Semaphore, SemaphoreSlim, ReaderWriterLockSlim |
Five threads each hold a slot for 1 s. Predict the total time.
static Semaphore sm = new Semaphore(2, 2); // 2 free slots, at most 2
static void Work()
{
string name = Thread.CurrentThread.Name;
sm.WaitOne(); // take a slot, or wait for one
Console.WriteLine(Now() + " " + name + " entered");
Thread.Sleep(1000);
Console.WriteLine(Now() + " " + name + " is leaving");
sm.Release(); // give the slot back
}0.0 s Thread 2 entered
0.0 s Thread 1 entered
1.0 s Thread 2 is leaving
1.0 s Thread 3 entered
1.0 s Thread 1 is leaving
1.0 s Thread 4 entered
2.0 s Thread 3 is leaving
2.0 s Thread 5 entered
2.0 s Thread 4 is leaving
3.0 s Thread 5 is leaving
All done at 3.0 sPart 4
A Windows Forms app has one UI thread
Application.Run(new Form1()) runs this loop on the UI thread
Windows
mouse, keys, paint
Message queue
first in, first out
Message pump
one message at a time
Your handler
button1_Click
The click handler sets the text, then works for 20 s. Predict what the window shows after 8 s.
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "Working";
Thread.Sleep(20 * 1000); // 20 s of work on the UI thread
textBox1.Text = "Done";
}

The click starts a background thread that writes 0 to 9 into textBox1, one per second. Predict what happens under the debugger (F5).
private void button1_Click(object sender, EventArgs e)
{
var thread = new Thread(StartCounting);
thread.IsBackground = true;
thread.Start();
}private void StartCounting()
{
for (var i = 0; i < 10; i++)
{
textBox1.Text = i.ToString();
Thread.Sleep(1000);
}
}
Control.CheckForIllegalCrossThreadCalls is False: the same code showed 3 after 3.5 sControl.CheckForIllegalCrossThreadCalls = true; in Main to behave like F5Invoke hands a delegate to the UI thread, which runs it through the message pump
private delegate void DisplayCountDelegate(int i);
private void StartCounting() // worker thread
{
for (var i = 0; i < 10; i++)
{
textBox1.Invoke(new DisplayCountDelegate(DisplayCount), i);
Thread.Sleep(1000);
}
button1.Invoke(new EnableButtonDelegate(EnableButton));
}private void DisplayCount(int i) // runs on the UI thread
{
textBox1.Text = i.ToString();
}
button1, so only one counting thread runs; EnableButton turns it back on through Invoke too3, and the window stays responsiveIts events run on the thread that created it: the UI thread
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += StartCounting; // worker thread
worker.ProgressChanged += worker_ProgressChanged; // UI thread
worker.RunWorkerCompleted += worker_RunWorkerCompleted; // UI thread
// StartCounting calls worker.ReportProgress(i) once a second
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox1.Text = e.ProgressPercentage.ToString();
}
InvokeRunWorkerAsync() starts DoWork on a worker threadRunWorkerCompleted re-enabled the button and wrote CompletedPart 5
About 30 minutes: predict on paper, then run it and check
About 6 minutes
static int balance = 500;ATM withdraws 300, thread App withdraws 400; Main joins both and prints Balancelock. Which outputs are then possible?static void Withdraw(int amount)
{
string name = Thread.CurrentThread.Name;
if (amount <= balance)
{
Console.WriteLine(name + " withdraws " + amount);
Thread.Sleep(100); // the bank's slow check
balance = balance - amount;
}
else
{
Console.WriteLine(name + ": not enough money");
}
}| Version | Output, 20 runs each |
|---|---|
| No lock | both withdraw, Balance: -200 (20 of 20) |
lock around the if | ATM withdraws 300, App: not enough money, Balance: 200 (19 of 20) |
lock around the if | App withdraws 400, ATM: not enough money, Balance: 100 (1 of 20) |
About 6 minutes
Two threads each run Increment; Main joins both and prints count. Predict count for A, B and C.
// A
lock (lockCount) { for (int i = 0; i < 500000; i++) count++; }
// B
for (int i = 0; i < 500000; i++) { lock (lockCount) { count++; } }
// C
for (int i = 0; i < 500000; i++)
{
int next = count + 1;
lock (lockCount) { count = next; }
}1000000?| Version | count, 12 runs each | Why |
|---|---|---|
| A | 1000000 in 12 | the whole loop is one critical section: one thread runs it all, then the other |
| B | 1000000 in 12 | every count++ is protected; the lock is released after each one, so the threads interleave |
| C | 12 different values, 491699 to 874943 | the read (count + 1) is outside the lock |
About 6 minutes
Four threads; each holds the lock for 3 pages of 200 ms and waits at most 1 s for it. Predict how many threads print.
Monitor.TryEnter(lockPrinter, TimeSpan.FromSeconds(1), ref lockTaken);
if (lockTaken)
{
for (int page = 1; page <= 3; page++)
{
Thread.Sleep(200); // one page takes 200 ms
}
Console.WriteLine(name + " printed 3 pages");
}
else
{
Console.WriteLine(name + " gave up");
}T1 printed 3 pages
T4 gave up
T2 gave up
T3 printed 3 pagesAbout 8 minutes
Play(string word): it prints word and a space, 5 timesPlay("ping"), then 100 ms later Play("pong")ping pong ping pong ping pong ping pong ping pong, then Game overMonitor.Enter, Pulse, Wait and Exit, as in the even and odd examplestatic void Play(string word)
{
try
{
Monitor.Enter(lockBall);
for (int i = 1; i <= rounds; i++) // rounds = 5
{
Console.Write(word + " ");
Monitor.Pulse(lockBall); // wake the other player
if (i != rounds)
Monitor.Wait(lockBall); // hand the ball over and wait
}
}ping pong ping pong ping pong ping pong ping pong
Game over finally
{
Monitor.Exit(lockBall);
}
}About 4 minutes
The semaphore example with new Semaphore(3, 3), six threads, and 2 s of work each. Predict the total time and the most threads inside at once.
0.0 s Thread 4 entered
0.0 s Thread 1 entered
0.0 s Thread 3 entered
2.0 s Thread 4 is leaving
2.0 s Thread 2 entered
...
4.0 s Thread 6 is leaving
All done at 4.0 scount++ is read, add, writelock lets one thread in at a time; it is Monitor.Enter + Exit in a try/finallyMonitor adds TryEnter (a timeout) and Wait/Pulse (taking turns)Semaphore lets a fixed number of threads in; a Mutex also works across programsInvoke or a BackgroundWorkerMahmoud Abas|C# Thread Synchronization