DA1. Multiple Thread Execution¶
Statement¶
- Discuss multiple threads executing the code (Figure 26.6, Chapter 26 Concurrency: An Introduction) which can result in a race condition, which is a critical section.
- A critical section is a piece of code that accesses a shared variable (or more generally, a shared resource) and must not be concurrently executed by more.
- How do you solve this issue when it occurs?
Answer¶
- the code mentioned in the question (Figure 26.6, Chapter 26 Concurrency: An Introduction) is doing the following:
- Creating two separate threads using
pthread_create()
function. - Passing different arguments to each thread,
1
and2
respectively; however, both threads are accessing the same variablecounter
and incrementing it by1
(million times each). - Waiting for both threads to finish using
pthread_join()
function; then exiting.
- Creating two separate threads using
- The
counter
variable is the critical section in this code since it is accessed and modified by two parallel threads. - The luck and the type of schedular used by the OS can affect the result of the execution of this code; however, the result will be different every time you run it making the result non-deterministic.
- The two threads may run:
- Arbitrarily, in any order; thread 1 may finish first, or thread 2 may finish first.
- They may pull the same value from the
counter
variable, and then increment it by1
and then write it back to thecounter
variable; which causes the counter to increase by 1 instead of 2. - Any issue may occur during one of the threads execution, which makes the results unpredictable.
-
To solve this issue we can use two techniques:
- Locks:
- we defined a lock variable and pass it to both threads; then when the first thread (can be A or B) accesses the critical section, it locks the lock variable and unlocks it once finished writing back to the
counter
variable. - Once the second thread starts execution, it checks if the lock variable is locked or not; if it is locked, it waits until the lock is unlocked, then it locks it and unlocks it once finished writing back to the
counter
variable.
- we defined a lock variable and pass it to both threads; then when the first thread (can be A or B) accesses the critical section, it locks the lock variable and unlocks it once finished writing back to the
- Signals (aka, semaphores or condition variables):
- we defined a signal variable and pass it to both threads; then when the first thread (can be A or B) accesses the critical section, it decrements the signal variable and increments it once finished writing back to the
counter
variable. - Once the second thread starts execution, it checks if the signal variable is
0
or not; if it is0
, it waits until the signal variable is incremented, then it decrements it and increments it once finished writing back to thecounter
variable.
- we defined a signal variable and pass it to both threads; then when the first thread (can be A or B) accesses the critical section, it decrements the signal variable and increments it once finished writing back to the
- Locks:
-
Below is the code for the first technique (locks):
#include <stdio.h>
#include <pthread.h>
#include "common.h"
#include "common_threads.h"
static volatile int counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 1. initialize the lock variable
void *mythread(void *arg) {
printf("%s: begin\n", (char *) arg);
pthread_mutex_lock(&lock); // 2. lock the lock variable or keep waiting until you acquire the lock
int i;
for (i = 0; i < 1e7; i++) {
counter = counter + 1;
}
printf("%s: done\n", (char *) arg);
pthread_mutex_unlock(&lock); // 3. unlock when done
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t p1, p2;
printf("main: begin (counter = %d)\n", counter);
Pthread_create(&p1, NULL, mythread, "A");
Pthread_create(&p2, NULL, mythread, "B");
// join waits for the threads to finish
Pthread_join(p1, NULL);
Pthread_join(p2, NULL);
printf("main: done with both (counter = %d)\n",
counter);
return 0;
}
- Below is the code for the second technique (signals):
#include <stdio.h>
#include <pthread.h>
#include "common.h"
#include "common_threads.h"
static volatile int counter = 0;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 1. initialize the condition helper variable
int signal = 1; // 2. initialize the signal variable
void *mythread(void *arg) {
printf("%s: begin\n", (char *) arg);
while (signal == 0) { // 3. check if the signal variable is 0 or not
pthread_cond_wait(&cond); // 4. if it is 0, wait until the signal variable is incremented
}
signal = 0; // 5. decrement the signal variable, preventing other threads from accessing the critical section
int i;
for (i = 0; i < 1e7; i++) {
counter = counter + 1;
}
printf("%s: done\n", (char *) arg);
signal = 1; // 6. increment the signal variable, allowing other threads to access the critical section
pthread_cond_signal(&cond); // 7. signal the other thread that the critical section is free
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t p1, p2;
printf("main: begin (counter = %d)\n", counter);
Pthread_create(&p1, NULL, mythread, "A");
Pthread_create(&p2, NULL, mythread, "B");
// join waits for the threads to finish
Pthread_join(p1, NULL);
Pthread_join(p2, NULL);
printf("main: done with both (counter = %d)\n",
counter);
return 0;
}
References¶
- Arpaci-Dusseau, R. H., & Arpaci-Dusseau, A. C. (2018). Operating systems: three easy pieces (1.01 ed.). Arpaci-Dusseau Books. https://pages.cs.wisc.edu/~remzi/OSTEP/