Lecture 12
Lecture 12
Multithreading Introduction
Optional reading:
Operating Systems: Principles and Practice (2 nd Edition): Chapter 4 and
Chapter 5 up through Section 5.1
This document is copyright (C) Stanford Computer Science and Nick Troccoli, licensed under
Creative Commons Attribution 2.5 License. All rights reserved.
Based on slides and notes created by John Ousterhout, Jerry Cain, Chris Gregg, and others.
NOTICE RE UPLOADING TO WEBSITES: This content is protected and may not be shared, 1
uploaded, or distributed. (without expressed written permission)
Topic 3: Multithreading - How
can we have concurrency within a
single process? How does the
operating system support this?
2
CS111 Topic 3: Multithreading
Multithreading - How can we have concurrency within a single process? How
does the operating system support this?
4
Learning Goals
• Learn about how threads allow for concurrency within a single process
• Understand the differences between threads and processes
• Discover some of the pitfalls of threads sharing the same virtual address space
5
Plan For Today
• Introducing multithreading
• Example: greeting friends
• Race conditions
• Threads share memory
• Example: selling tickets
cp -r /afs/ir/class/cs111/lecture-code/lect12 . 6
Plan For Today
• Introducing multithreading
• Example: greeting friends
• Race conditions
• Threads share memory
• Example: selling tickets
cp -r /afs/ir/class/cs111/lecture-code/lect12 . 7
From Processes to Threads
Multiprocessing has allowed us to spawn other processes to do tasks or run
programs
• Powerful; can execute/wait on other programs, secure (separate memory
space), communicate with pipes and signals
• But limited; interprocess communication is cumbersome, hard to share
data/coordinate
• Is there another way we can have concurrency beyond multiprocessing that
handles these tradeoffs differently?
8
From Processes to Threads
We can have concurrency within a single process using threads: independent
execution sequences within a single process.
• Threads let us run multiple functions in our program concurrently
• Multithreading is common to parallelize tasks, especially on multiple cores
• In C++: spawn a thread using thread() and the thread variable type and specify
what function you want the thread to execute (optionally passing parameters!)
• Each thread operates within the same process, so they share a virtual address
space (!) (globals, heap, pass by reference, etc.)
• The processes's stack segment is divided into a "ministack" for each thread.
• In the OS, threads are actually the unit of concurrency, not processes (more on
this later). Every process has at least 1 thread.
• Many similarities between threads and processes, but some key differences 9
Threads vs. Processes
Processes:
• isolate virtual address spaces (good: security and stability, bad: harder to share
info)
• can run external programs easily (fork-exec) (good)
• harder to coordinate multiple tasks within the same program (bad)
Threads:
• share virtual address space (bad: security and stability, good: easier to share
info)
• can't run external programs easily (bad)
• easier to coordinate multiple tasks within the same program (good)
10
Threads
Threads are a much more common approach to doing tasks in parallel on a
system because of easier data sharing and lighter weight creation. Many
modern software programs use multithreading:
• Mobile apps may spawn a background thread to download a web resource
while allowing another thread to handle user input (e.g. taps, swipes)
• A web server may spawn threads to handle incoming requests in parallel
• Your computer’s task manager can show you how many threads a process is
using
11
C++ Thread
A thread object can be spawned to run the specified function with the given
arguments.
thread myThread(myFunc, arg1, arg2, ...);
12
C++ Thread
To wait on a thread to finish, use the .join() method:
thread friends[5];
...
for (int i = 0; i < 5; i++) {
friends[i].join();
} 13
Plan For Today
• Introducing multithreading
• Example: greeting friends
• Race conditions
• Threads share memory
• Example: selling tickets
cp -r /afs/ir/class/cs111/lecture-code/lect12 . 14
Our First Threads Program
static void greeting(size_t i) {
cout << "Hello, world! I am thread " << i << endl;
}
...
friends.cc 15
Our First Threads Program
static const size_t kNumFriends = 6;
thread friends[kNumFriends];
for (size_t i = 0; i < kNumFriends; i++) {
friends[i] = thread(greeting, i);
}
Without threads, the calls are sequential; threads allow parallel execution.
17
C++ Thread
We can make an array of threads as follows:
// declare array of empty thread handles
thread friends[5];
// Spawn threads
for (size_t i = 0; i < 5; i++) {
friends[i] = thread(myFunc, arg1, arg2);
}
thread friends[5];
for (thread& currFriend : friends) {
currFriend = thread(myFunc, arg1, arg2);
} 18
Plan For Today
• Introducing multithreading
• Example: greeting friends
• Race conditions
• Threads share memory
• Example: selling tickets
cp -r /afs/ir/class/cs111/lecture-code/lect12 . 19
Race Conditions
• Like with processes, threads can execute in unpredictable orderings.
• A race condition is an ordering of events that causes undesired behavior.
• A thread-safe function is one that will always execute correctly, even when
called concurrently from multiple threads.
• printf is thread-safe, but operator<< is not. This means e.g. cout statements
could get interleaved!
• To avoid this, use oslock and osunlock (custom CS111 functions - #include
"ostreamlock.h") around streams. They ensure at most one thread has
permission to write into a stream at any one time.
cout << oslock << "Hello, world!" << endl << osunlock;
20
Our First Threads Program
static void greeting(size_t i) {
cout << oslock << "Hello, world! I am thread " << i << endl <<
osunlock;
}
...
friends.cc 21
Plan For Today
• Introducing multithreading
• Example: greeting friends
• Race conditions
• Threads share memory
• Example: selling tickets
cp -r /afs/ir/class/cs111/lecture-code/lect12 . 22
Threads Share Memory
Unlike parent/child processes, threads execute in the same virtual address space
• This means we can e.g. pass parameters by reference and have all threads
access/modify them!
• To pass by reference with thread(), we must use the special ref() function
around any reference parameters when we create a thread:
Let’s see another example of the potential for race condition problems.
24
Plan For Today
• Introducing multithreading
• Example: greeting friends
• Race conditions
• Threads share memory
• Example: selling tickets
cp -r /afs/ir/class/cs111/lecture-code/lect12 . 25
Parallelizing Tasks
Threads allow a process to parallelize a program across multiple cores.
• Consider a scenario where we want to sell 250 tickets and have 10 cores
• Simulation: let each thread help sell tickets until none are left
26
Parallelizing Tasks
Simulation: let each thread help sell the 250 tickets until none are left.
28
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
31
Thread #1 Thread #2
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
remainingTickets = 1
Are there tickets
to sell? Yep!
32
Thread #1 Thread #2
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
z remainingTickets = 1
z
z Are there tickets
to sell? Yep!
33
Thread #1 Thread #2
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
z remainingTickets = 1
z
z Are there tickets
to sell? Yep!
34
Thread #1 Thread #2
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
remainingTickets = 0
Let’s sell a ticket! z
z
z
35
Thread #1 Thread #2
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
remainingTickets = 0
Let’s sell a ticket! z
z
z
36
Thread #1 Thread #2
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
z remainingTickets = <really large number>
z
z Let’s sell a ticket!
37
Thread #1 Thread #2
Race Condition: Overselling Tickets
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
cout << oslock << "Thread #" << id << " sold a ticket ("
<< remainingTickets << " remain)." << endl << osunlock;
}
cout << oslock << "Thread #" << id
<< " sees no remaining tickets to sell and exits." << endl << osunlock;
}
z remainingTickets = <really large number>
z
z Let’s sell a ticket!
38
Thread #1 Thread #2
Race Condition: Overselling Tickets
There is a race condition here! Threads could interrupt each other in between
checking for remaining tickets and selling them.
static void sellTickets(size_t id, size_t& remainingTickets) {
while (remainingTickets > 0) {
sleep_for(500); // simulate "selling a ticket"
remainingTickets--;
...
}
...
}
• Even if we altered the code as below, it still wouldn’t fix the problem:
cp -r /afs/ir/class/cs111/lecture-code/lect12 . 43