CS 50 Software Design and Implementation
Lecture 22
POSIX Thread Programming or Pthreads
In the this lecture we study thread programming.
What is a thread? Well we have studied the forking of process to support concurrency. Threads are units
of control that execute within the context of a single process representing multiple strands of indepenent
execution.
What is the difference between forking processes and threads? Well typically when a process is forked it
executes as new independent process with its own PID and a copy of the code and resources of the parent
process. It is scheduled by the OS as a independent process. A process has a single thread by default called
main(). Threads running in a process get their own stack and run concurrently and share global variables
in the process.
We will need these skills for the robotics projects - we do a threaded design for a project.
In this lecture, we will just look at a number of simple examples of code to illustrate how threads are
created and how we can implement mutual exclusion using mutex for shared resouces. These notes are not
meant to be exhaustive - they are not.
For a in depth look at pthreads read the following tutorial - it may help answer questions that you may
have not covered in the class: POSIX Threads Programming, Blaise Barney, Lawrence Livermore National
Laboratory
Also, type “man pthread” for information on syntax, etc.
Goals
We plan to learn the following in today’s lecture:
- Creating a thread
- Random behavior
- Safe and unsafe threads
- Mutex
- Deadlock issues
Thread Creation
The code creates a thread; that means two threads are running - the main thread and the print_i
thread.
C code: print_i.c
// File print_i.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// global variable to share data
int i;
// This function will run concurrently.
void* print_i(void *ptr) {
while (1) {
sleep(1);
printf("%d\n", i);
}
}
int main() {
pthread_t t1;
i = 1;
int iret1 = pthread_create(&t1, NULL, print_i, NULL);
while (1) {
sleep(2);
i = i + 1;
}
exit(0); //never reached
}
Output from running the code
$ gcc -o print_i print_i.c
$ ./print_i
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
10
^Z
[1]+ Stopped ./print_i
Unpredicatability
In the following code what gets output first? We do not know what order the values will be
writtem out. If the main thread completes before the print_i thread has executed them it will
die.
C code: random.c
// File: random.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// This function will run concurrently.
void* print_i(void *ptr) {
printf("a\n");
printf("b\n");
}
int main() {
pthread_t t1;
int i = 1;
int iret1 = pthread_create(&t1, NULL, print_i, NULL);
printf("c\n");
}
Output from running the code
$ gcc -o random random.c
$ ./random
c
$ ./random
c
$ ./random
c
$ ./random
c
a
Functions that are thread unsafe
Typically libraries are thread safe. However many functions are said to be thread unsafe. As
below.
C code: unsafe.c
// File: unsafe.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
char s1[] = "abcdefg";
char s2[] = "abc";
char* c;
void last_letter(char* a, int i) {
printf("last letter a is %s and i is %d\n", a, i);
sleep(i);
c = NULL; // comment out a rerun, what is new?
sleep(i);
c = a;
sleep(i);
while (*(c)) {
c++;
}
printf("%c\n", *(c-1));
}
// This function will run concurrently.
void* aa(void *ptr) {
last_letter(s2, 2);
}
int main() {
pthread_t t1;
int iret1 = pthread_create(&t1, NULL, aa, NULL);
last_letter(s1, 5);
sleep(10);
printf(‘‘Ended nicely this time\n’’);
exit(0); //never reached when c = NULL is not commented out.
}
Output from running the code
$ gcc -o unsafe unsafe.c
$ ./unsafe
last letter a is abcdefg and i is 5
last letter a is abc and i is 2
Bus error
$# now comment out c = NULL and gcc again
$ gcc -o unsafe unsafe.c
$ ./unsafe
last letter a is abcdefg and i is 5
last letter a is abc and i is 2
c
g
Ended nicely this time
Mutex lock
Mutex is a special mechanism to help create concurrency in programs. Treat Mutex as a lock, there are
two functions. pthread_mutex_lock(mutex) and pthread_mutex_unlock(mutex).
pthread_mutex_lock(mutex): If the mutex is unlocked, this function will lock the mutex until
pthread_mutex_unlock(mutex) is called, and returns; otherwise, it will block until the lock is
unlocked;
pthread_mutex_unlock(mutex): If the mutex is locked by some pthread_mutex_lock() function, this
function will unlock the mutex; otherwise it does nothing.
See the following programs, what we want to do in this program is to ensure that at the same time there is
only one thread calling function print().
Typically libraries are thread safe. However many functions are said to be thread unsafe. As
below.
C code: mutex.c
// File: mutex.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
char s1[] = "abcdefg";
char s2[] = "abc";
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
// try uncommenting and commenting the mutext below
// and look at the output
void print(char* a, char* b) {
pthread_mutex_lock(&mutex1); // comment out
printf("1: %s\n", a);
sleep(1);
printf("2: %s\n", b);
pthread_mutex_unlock(&mutex1); // comment out
}
// These two functions will run concurrently.
void* print_i(void *ptr) {
print("I am", " in i");
}
void* print_j(void *ptr) {
print("I am", " in j");
}
int main() {
pthread_t t1, t2;
int iret1 = pthread_create(&t1, NULL, print_i, NULL);
int iret2 = pthread_create(&t2, NULL, print_j, NULL);
while(1){}
exit(0); //never reached.
}
Output from running the code
$ gcc -o mutex mutex.c
$ ./mutex
1: I am
2: in i
1: I am
2: in j
// With the mutex commented out the output is:
$ ./mutex
1: I am
1: I am
2: in i
2: in j
Deadlock
Deadlock is a classic problem that you can fall into with mutex threads.
C code: deadlock.c
// File: deadlock.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
// These two functions will run concurrently.
void* print_i(void *ptr) {
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);
printf("I am in i");
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
}
void* print_j(void *ptr) {
pthread_mutex_lock(&mutex2);
pthread_mutex_lock(&mutex1);
printf("I am in j");
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
}
int main() {
pthread_t t1, t2;
int iret1 = pthread_create(&t1, NULL, print_i, NULL);
int iret2 = pthread_create(&t2, NULL, print_j, NULL);
while(1){}
exit(0); //never reached.
}
Output from running the code
$ gcc -o deadlock deadlock.c
$ ./deadlock