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.
Summary
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:
- Thread creation is much faster.
- Context switching between threads is much faster.
- Threads can be terminated easily.
- 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.
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]
#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 threads to compute the integral of a function using the trapezoidal rule (opens in a new tab) with a uniform grid:
Use the following values: , , and , 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:
-
Extending the
Thread
class.MyThread.javaclass 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.javaclass Driver { public static void main(String[] args) { MyThread thread = new MyThread(•••); thread.start(); ••• try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
Implementing the
Runnable
interface.MyThread.javaclass 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.javaclass 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.