Lab 10 - Threading Part 1

A process is an independently executing program with its own memory space. Processes are isolated from each other and do not share memory by default (safer, but makes data exchange harder).

A thread is a line of execution inside a process. Threads in the same process share memory and resources (can communicate easily but need synchronisation tools to avoid corrupting shared data).

In C, we will be using threads via the POSIX pthread library (see documentation), which provides the necessary functions to create and manage threads.


Thread handling

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);

Click for explanation

int pthread_join(pthread_t thread, void **value_ptr);

Click for explanation

Example:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
 
void* myThreadFunction(void *arguments){
    printf("Thread running.\n");
    return NULL;
}
 
int main() {
    pthread_t thread1;
    pthread_create(&thread1, NULL, myThreadFunction, NULL);
    printf("Main Thread.\n");
    pthread_join(thread1, NULL); 
    return 0;
}

To compile, you will need to add -pthread to your compiler instructions.

Exercise 1

  1. Copy and paste the code above.
  2. What would happen if we didn't use pthread_join()? Try it.

Exercise 2

Code to copy

  1. Fill in the TODO to create 5 threads in a loop.
  2. Each thread should print its own index (0-4).

Hint: The final parameter of pthread_create() is a pointer which is passed to your function, so you will need to pass the ID number to the thread in this form.

Questions to consider:

  • Does simply passing a pointer to the variable i work? Why?
  • How could you modify the code so that each thread reliably prints its own index? (malloc/array of integers)

Mutexes

We can use mutexes to control access to shared resources, such as variables.

Mutex is a lock that ensures only one thread is modifying the protected data at a time. Only the thread that locks the mutex can unlock it.

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

Click for explanation

int pthread_mutex_lock(pthread_mutex_t *mutex);

Click for explanation

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Click for explanation

int pthread_mutex_destroy(pthread_mutex_t *mutex);

Click for explanation

Example:

The following code shows how a global variable can be modified safely using mutex:

#include <pthread.h>
#include <stdio.h>
 
int someVariable = 0;           // shared global variable
pthread_mutex_t lock;            // mutex to protect it
 
void* ThreadFunction(void* arg) {
    int* value = (int*) arg;           // cast the generic void* argument to int* so we can use it
 
    // Lock the mutex: only one thread can enter this section at a time
    pthread_mutex_lock(&lock);
 
    // Critical section: safe modification of shared variable
    someVariable = *value;
    printf("Thread updated someVariable to %d\n", someVariable);
 
    // Unlock the mutex so other threads can enter
    pthread_mutex_unlock(&lock);
 
    return NULL;
}
 
int main() {
    pthread_t t1, t2;
 
    // Initialize the mutex before using it
    pthread_mutex_init(&lock, NULL);
 
    // Create two threads that both try to modify someVariable
    int a = 1;
    int b = 2;
    pthread_create(&t1, NULL, ThreadFunction, &a);
    pthread_create(&t2, NULL, ThreadFunction, &b);
 
    // Wait for both threads to finish
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
 
    printf("Final value of someVariable: %d\n", someVariable);
 
    // Clean up the mutex
    pthread_mutex_destroy(&lock);
 
    return 0;
}

Exercise 3

Task 3.1

Use multiple threads to find the maximum in an array, updating a shared global variable protected by a mutex.

  1. Create one thread per element of the array
  2. Each thread
    • gets its number from the array
    • compares it with globalMax and if it is larger, updates globalMax

When comparing the values and updating the maximum value, use a mutex.

Code to copy

Task 3.2

In cases when we can't (or don't want to) use global variables, we might need to pass more than one value to the thread entry function.

The function pthread_create() has a single void* arg argument - we can bundle everything into a custom struct and pass a pointer to that struct.

  1. Reuse the code from Task 3.1
  2. Define and use struct ThreadArgs to carry: the value, a pointer to shared maximum and a pointer to the mutex
  3. Instead of passing a pointer to a single number, pass a pointer to a ThreadArgs struct into the thread function.

Code to copy

Task 3.3

Finally, extend the code so that you can read the exit value returned by each thread.

We can give pthread_join() a pointer to a pointer to a variable, where the return value of the thread function will be stored.

  1. Reuse the code from Task 3.2
  2. When joining threads, pass a pointer to a void* variable to pthread_join().
  3. In thread function, allocate memory for the return value, store the value there and return that pointer.
  4. The return value is a boolean representing whether the global max value was updated or not.

Code to copy

courses/be5b99cpl/labs/lab10.txt · Last modified: 2025/12/10 12:12 by janotjir