9 Thread Programming

Thread Programming

Objective

  • Introduce the concept of threads in operating systems.
  • Differentiate between physical concurrency and logical concurrency.
  • Understand a thread’s life cycle.
  • Create and execute threads using the POSIX thread library (Pthreads).
  • Create and execute threads using Java.

pthread_t pthread_attr_t malloc() pthread_attr_init() pthread_create() pthread_exit() pthread_join() Runnable Thread Runnable.run() Thread.join() Thread.run() Thread.sleep() Thread.start()

Threads

A thread is a lightweight task designated a portion of a process executing code. It is a single sequence stream within a process. Since threads have some of the properties of processes, they are sometimes called lightweight processes.

Multithreading is the capability of having a number of threads executing concurrently with each other within a single process. Threads are popular way to improve application through parallelism. Operating systems such as GNU/Linux, macOS, Windows, Solaris, and OS/2 support multiple threads of execution.

Multiple threads can execute independently:

  • They can run in parallel on multiple processors → Physical Concurrency.
  • Or arbitrarily interleaved on a single processor → Logical Concurrency.

Threads are not independent from each other, unlike processes. As a result, threads share with other threads their code section, data section and OS resources like open files and signals. But, like a process, a thread has its own program counter (PC), register set, and stack space.

Threads operate faster than processes due to following reasons:

  1. Thread creation is much faster.
  2. Context switching between threads is much faster.
  3. Threads can be terminated easily.
  4. Communication between threads is faster.

POSIX Threads

Pthreads refers to the POSIX standard defining an API for thread creation and synchronization. In order to use the POSIX threads you should include the <pthread.h> library.

UML state diagram for a threadreadyrunningwaitingcreateschedulepreemptsuspendresumecomplete
Figure 1: Simplified lifecycle of a thread

Example 1

Compile and run the following Pthreads program, in which a thread will be created to compute the factorial for a passed value, using:

cc -pthread example-01.c -o example-01
./example-01 [value]
example-01.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
 
int fact = 1;            // this data is shared by thread(s)
void *runp(void *param); // the thread function
 
int main(int argc, char *argv[]) {
  if (argc != 2) { // check number of arguments
    printf("usage: %s <integer value>\n", argv[0]);
    return -1;
  }
 
  if (atoi(argv[1]) < 0) { // check second passed argument
    printf("%d Must be >= 0\n", atoi(argv[1]));
    return -1;
  }
 
  pthread_t tid;            // the thread identifier
  pthread_attr_t attr;      // set of thread attributes
  pthread_attr_init(&attr); // get the default thread attributes
  pthread_create(&tid, &attr, runp, argv[1]); // create the thread
  pthread_join(tid, NULL);                    // wait for the thread to exit
 
  printf("%d! = %d\n", atoi(argv[1]), fact);
  return EXIT_SUCCESS;
}
 
void *runp(void *param) { // the thread will begin control in this function
  int i, num = atoi(param);
  for (i = num; i > 0; i--) {
    fact *= i;
  }
  pthread_exit(0); // thread exit
}

Exercise 1

Write a Pthreads program to compute the n multiplication table for the number x, where x and n are entered by user on the command line. Each created thread will calculate one element of the multiplication table and terminate. The parent thread should output the final result of the multiplication table after all threads finish.

The general syntax for dynamic memory/array allocation in C is:

type *arrayname = (type *)malloc(n * sizeof(type));

Do not forget to free the allocated memory using free(arrayname) when done, otherwise you will accidentally introduce a memory leak.

Exercise 2

Write a Pthreads program that computes the n adjacent sums for a list of numbers entered by the user. Each created thread will calculate the ith sum (in a new array) between the ith element and the (i + 1)th (next element).

For example, the cumulative sum of [1, 2, 3, 4] is [1, 3, 5, 7].

Exercise 3

Write a Pthreads program with nn threads to compute the integral of a function using the trapezoidal rule (opens in a new tab) with a uniform grid:

abf(x)dx=Δxk=1n12[f(xk1)+f(xk)]Δx=(ba)/nxk=a+kΔx\begin{align*} \int_a^b f(x)\,\mathrm{d}x &= \Delta x\sum_{k=1}^n \tfrac{1}{2} \big[f(x_{k-1})+f(x_k)\big] \\ \Delta x &= (b-a) / n \\ x_k &= a + k\Delta x \end{align*} UML activity diagram for joining threads

Use the following values: f(x)=expx2f(x) = \exp -x^2, a=1a = -1, and b=1b = 1, and compile your program using:

cc -pthread -lm exercise-03.c -o exercise-03

Java Threads

Creating and executing raw Java threads can be done through the following two ways:

  1. Extending the Thread class.

    MyThread.java
    class MyThread extends Thread {
      MyThread(Object param) {
        // store param for later use
        •••
      }
     
      @Override
      public void run() {
        // thread body of execution
        System.out.println(Thread.currentThread().getName());
        •••
      }
    }
    Driver.java
    class Driver {
      public static void main(String[] args) {
        MyThread thread = new MyThread(•••);
        thread.start();
        •••
        try {
          thread.join();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  2. Implementing the Runnable interface.

    MyThread.java
    class MyThread implements Runnable {
      MyThread(Object param) {
        // store param for later use
        •••
      }
     
      @Override
      public void run() {
        // thread body of execution
        System.out.println(Thread.currentThread().getName());
        •••
      }
    }
    Driver.java
    class Driver {
      public static void main(String[] args) {
        Thread thread = new Thread(new MyThread(•••));
        thread.start();
        •••
        try {
          thread.join();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }

Implementing the Runnable interface is the preferred way as the behavior of Thread is not being really specialized when extending it, and it still allows the specialization of another class, if needed.

FutureTask (opens in a new tab) and ThreadPoolExecutor (opens in a new tab) are classes that build on top of raw threads and it is highly recommended to use them for building multi-threaded applications.

Sleeping

The static method sleep() of class Thread places a thread into a timed-waiting state. At this point, the thread leaves the processor and the operating system allows another thread to execute. When the thread wakes up, it re-enters the runnable state and continues its execution.

To put a thread to sleep, use Thread.sleep(sleepTime) where sleepTime is in milliseconds.

Exercise 4

Write a Java threads program to reverse the elements of an array such that each created thread will swap two array elements and terminate. The parent thread should output the final result of the reversed array after all the threads terminate.

Resources