Search
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.
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
Click for explanation
Creates a new thread within a process.
NULL
void* func(void *)
int pthread_join(pthread_t thread, void **value_ptr);
Suspends execution of the calling thread until the target thread terminates (unless the target thread has already terminated).
void*
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.
-pthread
pthread_join()
Code to copy
#include <pthread.h> #include <stdio.h> #include <stdlib.h> void* printIndex(void* arg) { int* index = (int*) arg; printf("Thread index: %d\n", *index); return NULL; } int main() { pthread_t threads[5]; for (int i = 0; i < 5; i++) { // TODO: create a thread using pthread_create } for (int i = 0; i < 5; i++) { // TODO: join each thread } return 0; }
TODO
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.
pthread_create()
Questions to consider:
i
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);
Initialises the mutex referenced by mutex with attributes specified by attr.
int pthread_mutex_lock(pthread_mutex_t *mutex);
Locks the mutex object referenced by mutex.
If the mutex is already locked, the calling thread blocks until the mutex becomes available. This operation returns with the mutex object referenced by mutex in the locked state with the calling thread as its owner.
int pthread_mutex_unlock(pthread_mutex_t *mutex);
Releases the mutex object referenced by mutex.
int pthread_mutex_destroy(pthread_mutex_t *mutex);
Destroys the mutex object referenced by mutex.
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; }
Use multiple threads to find the maximum in an array, updating a shared global variable protected by a mutex.
globalMax
When comparing the values and updating the maximum value, use a mutex.
#include <pthread.h> #include <stdio.h> int numbers[] = {4, 7, 1, 9, 2}; int globalMax = 0; pthread_mutex_t lock; void* updateMax(void* arg) { int* value = (int*) arg; // cast the generic void* argument to int* // TODO: lock mutex // TODO: update globalMax if *value > globalMax // TODO: unlock mutex return NULL; } int main() { pthread_t threads[5]; pthread_mutex_init(&lock, NULL); for (int i = 0; i < 5; i++) { // TODO: create a thread for numbers[i] } for (int i = 0; i < 5; i++) { // TODO: join each thread } printf("Final global maximum: %d\n", globalMax); pthread_mutex_destroy(&lock); return 0; }
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.
void* arg
struct
ThreadArgs
#include <pthread.h> #include <stdio.h> #include <stdlib.h> typedef struct { int value; // number to consider int* globalMaxPtr; // pointer to the shared maximum pthread_mutex_t* lockPtr; // pointer to the mutex } ThreadArgs; void* updateMax(void* arg) { ThreadArgs* args = (ThreadArgs*) arg; // cast void* to our struct type // TODO: lock mutex // TODO: update maximum // TODO: unlock mutex // TODO: free args if they were allocated with malloc return NULL; } int main() { int numbers[] = {4, 7, 1, 9, 2}; int globalMax = 0; pthread_mutex_t lock; pthread_t threads[5]; pthread_mutex_init(&lock, NULL); for (int i = 0; i < 5; i++) { // TODO: allocate ThreadArgs (e.g. with malloc) // TODO: fill value, globalMaxPtr and lockPtr // TODO: create thread, passing the ThreadArgs pointer as argument } for (int i = 0; i < 5; i++) { // TODO: join each thread } printf("Final global maximum: %d\n", globalMax); pthread_mutex_destroy(&lock); return 0; }
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.
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef struct { int value; // number to consider int* globalMaxPtr; // pointer to the shared maximum pthread_mutex_t* lockPtr; // pointer to the mutex } ThreadArgs; void* updateMax(void* arg) { ThreadArgs* args = (ThreadArgs*) arg; // cast void* to our struct type // TODO: lock mutex // TODO: update maximum // TODO: unlock mutex // TODO: free args if they were allocated with malloc // TODO: allocate memory for the result and return it // bool* result = ... return result; // returned as void* } int main() { int numbers[] = {4, 7, 1, 9, 2}; int globalMax = 0; pthread_mutex_t lock; pthread_t threads[5]; pthread_mutex_init(&lock, NULL); for (int i = 0; i < 5; i++) { // TODO: allocate ThreadArgs (e.g. with malloc) // TODO: fill value, globalMaxPtr and lockPtr // TODO: create thread, passing the ThreadArgs pointer as argument } for (int i = 0; i < 5; i++) { void *ret; // TODO: join each thread and get ret bool* result = (bool*) ret; // cast ret back to int* printf("Thread %d returned exit value %d\n", numbers[i], *result); free(result); } printf("Final global maximum: %d\n", globalMax); pthread_mutex_destroy(&lock); return 0; }