Carnegie Mellon
Concurrent Programming
15-213: Introduction to Computer Systems 24rd Lecture, April 14, 2020
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
1
Carnegie Mellon
Concurrent Programming is Hard!
The human mind tends to be sequential
The notion of time is often misleading
Thinking about all possible sequences of events in a computer system is at least error prone and frequently impossible
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
2
Carnegie Mellon
Data Race
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
3
Carnegie Mellon
Deadlock
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
4
Carnegie Mellon
Deadlock
Example from signal handlers.
Why don’t we use printf in handlers?
void catch_child(int signo) {
printf(“Child exited!\n”); // this call may reenter printf/puts! BAD! DEADLOCK! while (waitpid(-1, NULL, WNOHANG) > 0) continue; // reap all children
}
Printf code:
Acquire lock Do something Release lock
Icurr lock Inext
signal
(Try to) acquire lock
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
5
Acquire Receive
Carnegie Mellon
Deadlock
Example from signal handlers.
Why don’t we use printf in handlers?
void catch_child(int signo) {
printf(“Child exited!\n”); // this call may reenter printf/puts! BAD! DEADLOCK! while (waitpid(-1, NULL, WNOHANG) > 0) continue; // reap all children
}
Printf code:
Acquire lock Do something Release lock
Icurr lock Inext
signal
(Try to) acquire lock
Acquire Receive
What if signal handler interrupts call to printf?
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
6
Deadlocked!
Carnegie Mellon
Testing Printf Deadlock
void catch_child(int signo) {
printf(“Child exited!\n”); // this call may reenter printf/puts! BAD! DEADLOCK! while (waitpid(-1, NULL, WNOHANG) > 0) continue; // reap all children
}
int main(int argc, char** argv) {
…
for (i = 0; i < 1000000; i++) {
if (fork() == 0) {
// in child, exit immediately
exit(0); }
// in parent
sprintf(buf, "Child #%d started\n", i); printf("%s", buf);
}
return 0; }
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
7
Child #0 started Child #1 started Child #2 started Child #3 started Child exited! Child #4 started Child exited! Child #5 started
. . .
Child #5888 started
Child #5889 started
Carnegie Mellon
Why Does Printf require Locks?
Printf (and fprintf, sprintf) implement buffered I/O
Buffered Portion
Current File Position
Require locks to access the shared buffers
no longer in buffer
already read
unread
unseen
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
8
Carnegie Mellon
Livelock
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
9
Carnegie Mellon
Livelock
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
10
Carnegie Mellon
Starvation
Yellow must yield to green
Continuous stream of green cars
Overall system makes progress, but some individuals wait indefinitely
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
11
Carnegie Mellon
Concurrent Programming is Hard!
Classical problem classes of concurrent programs:
Races: outcome depends on arbitrary scheduling decisions elsewhere in the system
Example: who gets the last seat on the airplane?
Deadlock: improper resource allocation prevents forward progress
Example: traffic gridlock
Livelock / Starvation / Fairness: external events and/or system
scheduling decisions can prevent sub-task progress Example: people always jump in front of you in line
Many aspects of concurrent programming are beyond the scope of our course..
but, not all
We’ll cover some of these aspects in the next few lectures. Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
12
Carnegie Mellon
Concurrent Programming is Hard! It may be hard, but ...
it can be useful and msoomretaimndesmnoerceensseacreys!sary!
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
13
Carnegie Mellon
Reminder: Iterative Echo Server
Client
socket
Server
socket
bind open_listenfd listen
open_clientfd
Client / Server Session
Await connection request from next client
connect
Connection request
accept
rio_writen
rio_readlineb
rio_writen
rio_readlineb
close
EOF
rio_readlineb
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
14
close
Carnegie Mellon
Iterative Servers
Iterative servers process one request at a time
Client 1
connect
write call read ret read
close
Server
accept read
write read
close
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
15
Carnegie Mellon
Iterative Servers
Iterative servers process one request at a time
Client 1
connect
write call read ret read
close
Server
Client 2
connect
write
call read
Wait for server to finish with Client 1
accept read
write read
close
accept
read
write
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
16
ret read
Carnegie Mellon
Where Does Second Client Block?
Second client attempts to connect to iterative server
Call to connect returns
Even though connection not
yet accepted
Server side TCP manager queues request
Feature known as “TCP listen backlog”
Call to rio_writen returns Server side TCP manager
buffers input data
Call to rio_readlineb blocks
Server hasn’t written anything for it to read yet.
Client
socket
open_clientfd
connect
rio_writen
rio_readlineb
Connection request
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
17
Carnegie Mellon
Fundamental Flaw of Iterative Servers
Client 1
connect
write
call read
ret read
User goes out to lunch
Client 1 blocks waiting for user to type in data
Server
accept
call read
write
call read
Server blocks waiting for data from Client 1
Client 2
connect
write
call read
Client 2 blocks waiting to read from server
Solution: use concurrent servers instead
Concurrent servers use multiple concurrent flows to serve multiple
clients at the same time
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
18
Carnegie Mellon
Approaches for Writing Concurrent Servers
Allow server to handle multiple clients concurrently
1. Process-based
Kernel automatically interleaves multiple logical flows Each flow has its own private address space
2. Event-based
Programmer manually interleaves multiple logical flows All flows share the same address space
Uses technique called I/O multiplexing
3. Thread-based
Kernel automatically interleaves multiple logical flows Each flow shares the same address space
Hybrid of of process-based and event-based
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
19
Carnegie Mellon
Approaches for Writing Concurrent Servers
Allow server to handle multiple clients concurrently
1. Process-based
Kernel automatically interleaves multiple logical flows Each flow has its own private address space
2. Event-based
Programmer manually interleaves multiple logical flows All flows share the same address space
Uses technique called I/O multiplexing
3. Thread-based
Kernel automatically interleaves multiple logical flows Each flow shares the same address space
Hybrid of of process-based and event-based
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
20
Carnegie Mellon
Approach #1: Process-based Servers Spawn separate process for each client
client 1
call connect
call fgets
User goes out to lunch
Client 1 blocks waiting for user to type in data
child 1
call read
Child blocks waiting for data from Client 1
server
call accept
ret accept
fork
call accept
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
21
Carnegie Mellon
Approach #1: Process-based Servers Spawn separate process for each client
client 1
call connect
call fgets
User goes out to lunch
Client 1 blocks waiting for user to type in data
server
client 2
call connect
child 1
call read
Child blocks waiting for data from Client 1
call accept
ret accept
fork
call accept ret accept
fork
call fgets
write
call read
ret read
close
child 2 ... call
read
write close
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
22
Carnegie Mellon
Iterative Echo Server
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
23
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
echo(connfd);
Close(connfd);
}
exit(0); }
Accept a connection request
Handle echo requests until client terminates
echoserverp.c
Carnegie Mellon
Making a Concurrent Echo Server
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
24
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
listenfd = Open_listenfd(argv[1]);
while (1) {
} }
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
echo(connfd); /* Child services client */
Close(connfd); /* child closes connection with client */
exit(0);
echoserverp.c
Carnegie Mellon
Making a Concurrent Echo Server
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
25
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
Signal(SIGCHLD, sigchld_handler);
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
if (Fork() == 0) {
Close(listenfd); /* Child closes its listening socket */
} }
}
echo(connfd);
Close(connfd);
exit(0);
/* Child services client */
/* Child closes connection with client */
/* Child exits */
Close(connfd); /* Parent closes connected socket (important!) */
echoserverp.c
Carnegie Mellon
Making a Concurrent Echo Server
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
26
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
Signal(SIGCHLD, sigchld_handler);
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
if (Fork() == 0) {
Close(listenfd); /* Child closes its listening socket */
} }
}
echo(connfd);
Close(connfd);
exit(0);
/* Child services client */
/* Child closes connection with client */
/* Child exits */
Close(connfd); /* Parent closes connected socket (important!) */
echoserverp.c
Why?
Carnegie Mellon
Making a Concurrent Echo Server
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
27
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
Signal(SIGCHLD, sigchld_handler);
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
if (Fork() == 0) {
Close(listenfd); /* Child closes its listening socket */
}
echo(connfd);
Close(connfd);
exit(0);
/* Child services client */
/* Child closes connection with client */
/* Child exits */
Close(connfd); /* Parent closes connected socket (important!) */
}
}
echoserverp.c
Carnegie Mellon
Process-Based Concurrent Echo Server
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
28
int main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
Signal(SIGCHLD, sigchld_handler);
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
if (Fork() == 0) {
Close(listenfd); /* Child closes its listening socket */
}
echo(connfd);
Close(connfd);
exit(0);
/* Child services client */
/* Child closes connection with client */
/* Child exits */
Close(connfd); /* Parent closes connected socket (important!) */
}
}
echoserverp.c
Carnegie Mellon
Process-Based Concurrent Echo Server (cont)
void sigchld_handler(int sig)
{
while (waitpid(-1, 0, WNOHANG) > 0)
;
return; }
echoserverp.c
Reap all zombie children
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
29
Carnegie Mellon
Concurrent Server: accept Illustrated
Client
clientfd
Connection request
Client
clientfd
listenfd(3)
Server
1. Server blocks in accept, waiting for connection request on listening descriptor listenfd
2. Client makes connection request by calling connect
3. Server returns connfd from accept. Forks child to handle client. Connection is now established between clientfd and connfd
listenfd(3)
Server
listenfd(3)
Server
Client
clientfd
connfd(4)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
30
Server Child
Carnegie Mellon
Process-based Server Execution Model
Connection requests
Listening server process
Client 1 server process
Client 1 data Client 2 data
Each client handled by independent child process
No shared state between them
Both parent & child have copies of listenfd and connfd Parent must close connfd
Child should close listenfd
Client 2 server process
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
31
Carnegie Mellon
Issues with Process-based Servers
Listening server process must reap zombie children
to avoid fatal memory leak
Parent process must close its copy of connfd
Kernel keeps reference count for each socket/open file
After fork, refcnt(connfd) = 2
Connection will not be closed until refcnt(connfd)
int main(int argc, char **argv) {
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
listenfd = Open_listenfd(argv[1]);
while (1) {
= 0
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
32
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
if (Fork() == 0) {
}
echo(connfd);
Close(connfd);
exit(0);
/* Child services client */
/* Child closes connection with clien
/* Child exits */
Carnegie Mellon
Pros and Cons of Process-based Servers
+ Handle multiple connections concurrently
+ Clean sharing model descriptors (no)
file tables (yes)
global variables (no)
+ Simple and straightforward
– Additional overhead for process control
– Nontrivial to share data between processes (This example too simple to demonstrate)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
33
Carnegie Mellon
Approach #2: Event-based Servers Server maintains set of active connections
Array of connfd’s Repeat:
Determine which descriptors (connfd’s or listenfd) have pending inputs
e.g., using select function
arrival of pending input is an event
If listenfd has input, then accept connection
and add new connfd to array
Service all connfd’s with pending inputs
Details for select-based server in book Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
34
Carnegie Mellon
I/O Multiplexed Event Processing
Active Descriptors
Read and service Pending Inputs
listenfd = 3
connfd’s
listenfd = 3
connfd’s
10
7
4
-1
-1
12
5
-1
-1
-1
10
7
4
-1
-1
12
5
-1
-1
-1
0 1 2 3 4 5 6 7 8 9
Active
Inactive Active
Never Used
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
35
Anything happened?
Carnegie Mellon
Pros and Cons of Event-based Servers
+ One logical control flow and address space.
+ Can single-step with a debugger.
+ No process or thread control overhead.
Design of choice for high-performance Web servers and search engines.
e.g., Node.js, nginx, Tornado
– Significantly more complex to code than process- or thread- based designs.
– Hard to provide fine-grained concurrency
E.g., how to deal with partial HTTP request headers
– Cannot take advantage of multi-core Single thread of control
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
36
Carnegie Mellon
Quiz Time!
Check out:
https://canvas.cmu.edu/courses/13182
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
37
Carnegie Mellon
Approach #3: Thread-based Servers Very similar to approach #1 (process-based)
…but using threads instead of processes
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
38
Carnegie Mellon
Traditional View of a Process
Process = process context + code, data, and stack
Process context
Code, data, and stack
SP
brk
PC
0
Stack
Shared libraries
Run-time heap
Read/write data
Read-only code/data
Program context:
Data registers Condition codes Stack pointer (SP) Program counter (PC)
Kernel context: VM structures
Descriptor table brk pointer
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
39
Carnegie Mellon
Alternate View of a Process
Process = thread + code, data, and kernel context
Thread (main thread)
Code, data, and kernel context
brk
PC
0
Shared libraries
Run-time heap
Read/write data
Read-only code/data
SP
Stack
Thread context: Data registers
Condition codes Stack pointer (SP) Program counter (PC)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
40
Kernel context: VM structures
Descriptor table brk pointer
Carnegie Mellon
A Process With Multiple Threads
Multiple threads can be associated with a process
Each thread has its own logical control flow
Each thread shares the same code, data, and kernel context Each thread has its own stack for local variables
but not protected from other threads Each thread has its own thread id (TID)
Thread 1 (main thread) Thread 2 (peer thread)
stack 1 stack 2
Shared code and data
shared libraries
run-time heap
read/write data
read-only code/data
Thread 1 context: Data registers Condition codes SP1
PC1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
41
Thread 2 context: Data registers Condition codes SP2
PC2
0
Kernel context: VM structures
Descriptor table brk pointer
Carnegie Mellon
Logical View of Threads
Threads associated with process form a pool of peers
Unlike processes which form a tree hierarchy
Threads associated with process foo
Process hierarchy P0
P1
sh sh sh foo
bar
T2 T1
T4
shared code, data and kernel context
T5
T3
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
42
Carnegie Mellon
Concurrent Threads
Two threads are concurrent if their flows overlap in time
Otherwise, they are sequential
Examples:
Concurrent: A & B, A&C Sequential: B & C
Time
Thread A
Thread B
Thread C
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
43
Carnegie Mellon
Concurrent Thread Execution
Single Core Processor Simulate parallelism by
time slicing
Thread B
Multi-Core Processor Can have true
parallelism
Thread A Thread B Thread C
Thread A
Thread C
Time
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
44
Run 3 threads on 2 cores
Carnegie Mellon
Threads vs. Processes
How threads and processes are similar Each has its own logical control flow
Each can run concurrently with others (possibly on different cores) Each is context switched
How threads and processes are different
Threads share all code and data (except local stacks) Processes (typically) do not
Threads are somewhat less expensive than processes
Process control (creating and reaping) twice as expensive as thread
control
Linux numbers:
– ~20K cycles to create and reap a process
– ~10K cycles (or less) to create and reap a thread
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
45
Carnegie Mellon
Threads vs. Signals
Receive
Icurr signal Handler
Inext
Signal handler shares state with regular program Including stack
Signal handler interrupts normal program execution Unexpected procedure call
Returns to regular execution stream Not a peer
Limited forms of synchronization
Main program can block / unblock signals
Main program can pause for signal Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
46
Carnegie Mellon
Posix Threads (Pthreads) Interface
Pthreads: Standard interface for ~60 functions that manipulate threads from C programs
Creating and reaping threads pthread_create() pthread_join()
Determining your thread ID pthread_self()
Terminating threads
pthread_cancel()
pthread_exit()
exit() [terminates all threads] return [terminatescurrentthread]
Synchronizing access to shared variables pthread_mutex_init
pthread_mutex_[un]lock
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
47
Carnegie Mellon
The Pthreads “hello, world” Program
/*
* hello.c – Pthreads “hello, world” program
*/
#include “csapp.h”
void *thread(void *vargp);
int main(int argc, char** argv)
{
Thread ID
Thread routine
pthread_t tid;
Pthread_create(&tid, NULL, thread, NULL);
Pthread_join(tid, NULL);
return 0;
}
hello.c
Thread attributes (usually NULL)
Thread arguments (void *p)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
48
Return value (void **p)
void *thread(void *vargp) /* thread routine */
{
printf(“Hello, world!\n”);
return NULL;
}
hello.c
Carnegie Mellon
Execution of Threaded “hello, world”
Main thread
call Pthread_create() Pthread_create()returns
call Pthread_join() Main thread waits for
peer thread to terminate
Pthread_join()returns
exit()
Terminates main thread and
any peer threads
Peer thread
printf()
return NULL;
Peer thread terminates
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
49
Carnegie Mellon
Or, …
Main thread
call Pthread_create() Pthread_create()returns
call Pthread_join()
Main thread doesn’t need to wait for peer thread to terminate
Pthread_join()returns
exit()
Terminates main thread and
any peer threads
Peer thread
printf()
return NULL;
Peer thread terminates
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
50
And many many more possible ways for this code to execute.
Carnegie Mellon
Thread-Based Concurrent Echo Server
int main(int argc, char **argv)
{
int listenfd, *connfdp;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
pthread_t tid;
listenfd = Open_listenfd(argv[1]);
while (1) {
clientlen=sizeof(struct sockaddr_storage);
connfdp = Malloc(sizeof(int));
*connfdp = Accept(listenfd, (SA *) &clientaddr, &clientlen);
Pthread_create(&tid, NULL, thread, connfdp);
}
return 0; echoservert.c }
Spawn new thread for each client
Pass it copy of connection file descriptor
Note use of Malloc()! [but not Free()]
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
51
Carnegie Mellon
Thread-Based Concurrent Server (cont)
/* Thread routine */
void *thread(void *vargp) {
int connfd = *((int *)vargp);
Pthread_detach(pthread_self());
Free(vargp);
echo(connfd);
Close(connfd);
return NULL;
}
echoservert.c
Run thread in “detached” mode.
Runs independently of other threads
Reaped automatically (by kernel) when it terminates
Free storage allocated to hold connfd Close connfd (important!)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
52
Carnegie Mellon
Thread-based Server Execution Model
Connection requests
Client 1 data Client 2 data
Listening server main thread
Client 1 server
peer thread
Client 2 server peer thread
Each client handled by individual peer thread
Threads share all process state except TID
Each thread has a separate stack for local variables
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
53
Carnegie Mellon
Issues With Thread-Based Servers
Must run “detached” to avoid memory leak
At any point in time, a thread is either joinable or detached Joinable thread can be reaped and killed by other threads
must be reaped (with pthread_join) to free memory resources Detached thread cannot be reaped or killed by other threads
resources are automatically reaped on termination Default state is joinable
use pthread_detach(pthread_self()) to make detached
Must be careful to avoid unintended sharing
For example, passing pointer to main thread’s stack
Pthread_create(&tid, NULL, thread, (void *)&connfd);
All functions called by a thread must be thread-safe (next lecture)
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
54
Carnegie Mellon
Potential Form of Unintended Sharing
while (1) {
int connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
Pthread_create(&tid, NULL, thread, &connfd);
}
main thread
connfd = connfd1
connfd = connfd2
Main thread stack
connfd
peer1
Race!
peer2
connfd = *vargp
Peer1 stack
vargp
Peer2 stack
vargp
Why would both copies of vargp point to same location?
55
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
connfd = *vargp
Carnegie Mellon
A Process With Multiple Threads
Multiple threads can be associated with a process
Each thread has its own logical control flow
Each thread shares the same code, data, and kernel context Each thread has its own stack for local variables
but not protected from other threads Each thread has its own thread id (TID)
Thread 1 (main thread) Thread 2 (peer thread)
stack 1 stack 2
Shared code and data
shared libraries
run-time heap
read/write data
read-only code/data
Thread 1 context: Data registers Condition codes SP1
PC1
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
56
Thread 2 context: Data registers Condition codes SP2
PC2
0
Kernel context: VM structures
Descriptor table brk pointer
But ALL memory is shared
Carnegie Mellon
Thread 1 context: Data registers Condition codes SP1
PC1
Thread 2 context: Data registers Condition codes SP2
PC2
Thread 1 (main thread) Thread 2 (peer thread)
stack 1 stack 2
shared libraries
run-time heap
read/write data
read-only code/data
0
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
57
Kernel context: VM structures
Descriptor table brk pointer
Carnegie Mellon
while (1) {
int connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
Pthread_create(&tid, NULL, thread, &connfd);
}
Thread 1
Thread 2
shared libraries
run-time heap
read/write data
read-only code/data
&connfd
connfd
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
58
0
Kernel context: VM structures
Descriptor table brk pointer
Thread 2 context: Data registers Condition codes SP2
PC2
Thread 1 context: Data registers Condition codes SP1
PC1
Thread 1 context: Data registers Condition codes SP1
PC1
Carnegie Mellon
while (1) {
int connfd = Accept(listenfd, (SA *) &clientaddr, &clientlen);
Pthread_create(&tid, NULL, thread, &connfd);
}
Thread 2 context: Data registers Condition codes SP2
PC2
Thread 3 context: Data registers Condition codes SP2
PC2
Thread 1
Thread 2 Thread 3
shared libraries
run-time heap
read/write data
read-only code/data
&connfd
&connfd
connfd
0
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
59
Kernel context: VM structures
Descriptor table brk pointer
/* Thread routine */
void *thread(void *vargp) Thread 3 context:
Thread 1 context: Data registers Condition codes SP1
PC1
{
Data registers
int connfd = *((int *)vargp) Condition codes
SP2 PC2
Pthread_detach(pthread_self(
Free(vargp);
echo(connfd);
Close(connfd);
return NULL; Thread 3
shared libraries
Carnegie Mellon
Thread 1
Thread 2
}
&connfd
&connfd
connfd
run-time heap
read/write data
read-only code/data
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
6
Thread 2 context: Data registers Condition codes SP2
PC2
0
Kernel context: VM structures
Descriptor table brk pointer
0
; )
Carnegie Mellon
Could this race occur?
Main Thread
int i;
for (i = 0; i < 100; i++) {
Pthread_create(&tid, NULL, thread, &i);
}
void *thread(void *vargp)
{
int i = *((int *)vargp); Pthread_detach(pthread_self()); save_value(i);
return NULL;
}
Race Test
If no race, then each thread would get different value of i
Set of saved values would consist of one copy each of 0 through 99
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
61
Carnegie Mellon
Experimental Results No Race
2 1 0
3 2 1 0
Multicore server
14 12 10
8 6 4 2 0
0 2 4 6 8 101214161820222426283032343638404244464850525456586062646668707274767880828486889092949698
Single core laptop
0 2 4 6 8 101214161820222426283032343638404244464850525456586062646668707274767880828486889092949698
0 2 4 6 8 101214161820222426283032343638404244464850525456586062646668707274767880828486889092949698
The race can really happen!
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
62
Carnegie Mellon
Correct passing of thread arguments
/* Main routine */
int *connfdp;
connfdp = Malloc(sizeof(int));
*connfdp = Accept( . . . );
Pthread_create(&tid, NULL, thread, connfdp);
/* Thread routine */
void *thread(void *vargp)
{
int connfd = *((int *)vargp); .. .
Free(vargp); . ..
return NULL;
}
Producer-Consumer Model Allocate in main
Free in thread routine
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
63
Carnegie Mellon
Pros and Cons of Thread-Based Designs
+ Easy to share data structures between threads
e.g., logging information, file cache
+ Threads are more efficient than processes
– Unintentional sharing can introduce subtle and hard- to-reproduce errors!
The ease with which data can be shared is both the greatest strength and the greatest weakness of threads
Hard to know which data shared & which private
Hard to detect by testing
Probability of bad race outcome very low But nonzero!
Future lectures
Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
64
Carnegie Mellon
Summary: Approaches to Concurrency
Process-based
Hard to share resources: Easy to avoid unintended sharing High overhead in adding/removing clients
Event-based
Tedious and low level
Total control over scheduling
Very low overhead
Cannot create as fine grained a level of concurrency Does not make use of multi-core
Thread-based
Easy to share resources: Perhaps too easy
Medium overhead
Not much control over scheduling policies
Difficult to debug
Event orderings not repeatable Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
65