$29
1. Prerequisites
1. Successful installation of *nix terminal (e.g., WSL, cygwin) or a *nix operating system (e.g., Linux, Mac).
2. Successful installation of a C compiler, GCC is recommended.
3. Successful installation of a text editor, VIM or Emacs is recommended.
2. Exercises
When multiple threads or processes access shared resources (i.e., critical region or critical section), there is a possibility of race conditions or inconsistent behavior. For example, if two threads simultaneously try to modify a shared variable, the result of the operation may depend on the order in which the threads execute, which may lead to incorrect results.
In this exercise, students are required to complete the following codes to implement several basic operating system synchronization algorithms. For each program, students should capture screenshots of the successful execution, and answer the questions for the program. In the submission file, please attach the added code and execution screenshot in sequence.
Task:
Suppose that a shared resource can be accessed by several threads, please use a global variable “counter” to count the number of accesses to the resource.
Here is the sample code:
1 // Global variables
2 static long counter = 0;
3 void *thread(void *vargp) {
4 // Enter critical region
5 for (long i = 0; i < N_ITERATIONS; i++) {
6 /* Shared resource */
7 counter++;
8 }
9 // Leave critical region
10 return NULL;
11 }
Example 1: Critical region
This example shows what are the race condition and critical region.
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <pthread.h>
15
16 #define NUM_THREADS 2
17 #define N_ITERATIONS 10000000 // number of iterations
18
19 // Global variables
20 static volatile long counter = 0;
21
22 void *thread(void *vargp) {
23
24 // Enter critical region
25 for (long i = 0; i < N_ITERATIONS; i++) {
26 /* Shared resource */
27 counter++;
28 }
29 // Leave critical region
30
31 return NULL;
32 }
33
34 int main(int argc, char **argv) {
35 pthread_t tid1, tid2;
36
37 pthread_create(&tid1, NULL, thread, NULL);
38 pthread_create(&tid2, NULL, thread, NULL);
39 pthread_join(tid1, NULL);
40 pthread_join(tid2, NULL);
41
42 if (counter != (2 * N_ITERATIONS))
43 printf("Wrong! counter=%ld.\n", counter);
44 else
45 printf("Correct! counter=%ld.\n", counter);
46
47 return 0;
48 }
Code:
1. Download the sample code from UMMoodle (critical_region.c).
2. Compile the above C program and run several times. (Tips: use the following compile command.)
gcc critical_region.c -o critical_region -l pthread.
Question 1:
1.1 Why the counter value is "Wrong"?
1.2 What happens if the number of iterations is reduced?
Requirements:
1. Capture 2 screenshots of different outputs.
2. Answer Question 1.
Example 2: Alternation algorithm
In practice, we will implement mutual exclusion via some algorithms to synchronize access to shared resources, and the Alternation algorithm is the simplest method among them. Complete the code at the blank underline to implement the Alternation algorithm and answer questions.
Code:
1. Download the sample code from UMMoodle (alternation.c).
2. Compile the above C program and run several times. (Tips: use the following compile command.)
gcc alternation.c -o alternation -l pthread.
Incomplete code
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4
5 #define TRUE 1
6 #define NUM_THREADS 2
7 #define N_ITERATIONS 100000000
8
9 // Global variables
10 static volatile long counter = 0;
11 static volatile int turn = 1;
12
13
14 // Thread 1
15 void *thread_1(void *arg) {
16 while (TRUE) {
17
while (turn !=
??? ){
18 // Wait
19 }
20
21 // Enter critical region
22 for (long i = 0; i < N_ITERATIONS; i++)
23 counter++;
24
25 // Leave critical region
26
turn =
???
;
27
28 break;
29 }
30
31 pthread_exit(NULL);
32 }
33
34 // Thread 2
35 void *thread_2(void *arg) {
36
37 while (TRUE) {
38
while (turn !=
??? ){
39 // Wait
40 }
41
42 // Enter critical region
43 for (long i = 0; i < N_ITERATIONS; i++)
44 counter++;
45
turn =
???
;
46 // Leave critical region
47
48 break;
49 }
50
51 return NULL;
52 }
53
54 int main() {
55 pthread_t tid1, tid2;
56
57 pthread_create(&tid1, NULL, thread_1, NULL);
58 pthread_create(&tid2, NULL, thread_2, NULL);
59 pthread_join(tid1, NULL);
60 pthread_join(tid2, NULL);
61
62 printf("counter=%ld.\n", counter);
63
64 return 0;
65 }
Question 2:
2.1 Please complete the code for the alternation algorithm.
2.2 Why does it satisfy the mutual exclusion?
2.3 What are the drawbacks of the alternation algorithm?
Requirements:
1. Capture 1 screenshots of the successful execution.
2. Answer Question 2.
Example 3: Peterson’s algorithm
Peterson’s algorithm is a more flexible method. Complete the code at the blank underline to implement the Peterson’s algorithm and answer questions.
Code:
1. Download the sample code from UMMoodle (peterson.c).
2. Compile the above C program and run several times. (Tips: use the following compile command.)
gcc peterson.c -o peterson -l pthread.
Incomplete code
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4
5 #define ENTER 1
6 #define LEAVE 0
7 #define NUM_THREADS 2
8 #define N_ITERATIONS 10000000
9
10 // Global variables
11 static volatile long counter = 0;
12 static volatile int turn = 0;
13 static volatile int flag[NUM_THREADS] = {0, 0};
14
15 // Increment function
16 void *increment(void *arg) {
17 int thread_id = *(int*)arg;
18
19 flag[thread_id] = ENTER;
20
turn =
???
;
21
while (flag[
???
] && turn ==
??? ){
22 // Wait
23 }
24
25 // Enter critical region
26 for (long i = 0; i < N_ITERATIONS; i++) {
27 counter++;
28 }
29
30 // Leave critical section
31 flag[thread_id] = LEAVE;
32
33 return NULL;
34 }
35
36 int main() {
37 pthread_t threads[NUM_THREADS];
38 int thread_ids[NUM_THREADS];
39
40 // Create threads
41 for (int i = 0; i < NUM_THREADS; i++) {
42 thread_ids[i] = i;
43 pthread_create(&threads[i], NULL, increment, &thread_ids[i]);
44 }
45
46 // Wait for threads to finish
47 for (int i = 0; i < NUM_THREADS; i++) {
48 pthread_join(threads[i], NULL);
49 }
50
51 printf("counter=%ld.\n", counter);
52
53 return 0;
54 }
Question 3:
3.1 Please complete the code for the Peterson’s algorithm.
3.2 Why does it satisfy the mutual exclusion?
Requirements:
1. Capture 1 screenshots of the successful execution.
2. Answer Question 3.
Example 4: Lock
Locks allow threads or processes to acquire exclusive access to shared resources, ensuring that only one thread or process can access the shared resource at a time. This prevents race conditions and ensures the correctness of the program. This example shows how to implement a spinlock.
Code:
1. Download the sample code from UMMoodle (lock.c).
2. Test 2 lock algorithms. Do they both work?
1 /***********1st algorithm*******/
2 while (*lock) {
3 // Wait
4 }
5 *lock = TRUE;
6 /*******************************/
7
8 /*********2nd algorithm*********/
9 while (test_and_set(lock, TRUE) == TRUE) {
10 // Wait
11 }
12 /*******************************/
3. Compile the above C program and run several times. (Tips: use the following compile command.)
gcc lock.c -o lock -l pthread.
Question 4:
4.1 What's the difference between the 2 lock algorithms in the "acquire" function?
4.2 Does the 1st algorithm work? Why?
4.3 Does the 2nd algorithm work? How does the "test and set" operation work?
Requirements:
1. Capture 2 screenshots of the 2 lock algorithms.
2. Answer Question 4.