CS 23 Software Design and Implementation

Lecture 22

POSIX Thread Programming

In the this lecture we study thread programming.

What is a thread? Well we have studied the forking of process to support concurrency. Thread 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. 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.

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 mute for shared resouces. These notes are not meant to be exhaustive - they are not.

Goals

We plan to learn the following from today’s lecture:

Thread Creation

The code creates a thread; that means two threads are running - the main thread and the print˙i thread.


// File print_1.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


atc@Macintosh-25 l22] ./thread
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
10

etc.

Unpredicatability

What get output first? We do not know what order the values will be writtem out. If the main tread completes beore the print˙i thread has executed them it will die.


// 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


atc@Macintosh-25 l22] ./thread
c

Functions threaded unsafe

Typically libraries are thread safe. However many functions are said to be thread unsafe. As below.


// File: unsafe threads

#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;
  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);
  exit(0); //never reached.
}

Output from running the code


[atc@Macintosh-25 l22] gcc -o thread unsafe.c
[atc@Macintosh-25 l22] ./thread
last letter a is abcdefg and i is 5
last letter a is abc and i is 2
Bus error

Mutex lock

Mutex is a special mechanism to help creating concurrency 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.


// 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;

void print(char* a, char* b) {
  pthread_mutex_lock(&mutex1);
  printf("1: %s\n", a);
  sleep(1);
  printf("2: %s\n", b);

  pthread_mutex_unlock(&mutex1);

}


// 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


[atc@Macintosh-25 l22] ./thread
1: I am
2:  in i
1: I am
2:  in j

// With the mutex commented out the output is:

[atc@Macintosh-25 l22] ./thread
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.


// 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


[atc@Macintosh-25 l22] ./thread