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:

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