Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions chapters/compute/overview/reading/lab6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The contents of the lab are located in the [lab archive](https://github.com/cs-pub-ro/operating-systems/raw/refs/heads/lab-archives/Lab_6_Multiprocess_and_Multithread.zip) and in the [GitHub repository](https://github.com/cs-pub-ro/operating-systems).
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Create Process

Enter the `chapters/compute/processes/drills/tasks/create-process/` directory, run `make skels`, open the `support/src` folder and go through the practice items below.
Enter the `create-process/` directory (or `chapters/compute/processes/drills/tasks/create-process/` if you are working directly in the repository).
Run `make` and then enter `support/` folder and go through the practice items below.

Use the `tests/checker.sh` script to check your solutions.

```bash
./checker.sh
./tests/checker.sh
exit_code22 ...................... passed ... 50
second_fork ...................... passed ... 50
100 / 100
Expand Down
5 changes: 3 additions & 2 deletions chapters/compute/processes/drills/tasks/sleepy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

## Higher level - Python

Enter the `chapters/compute/processes/drills/tasks/sleepy` directory, run `make skels`, open the `support/src` folder and go through the practice items below.
Enter the `sleepy/` directory (or `chapters/compute/processes/drills/tasks/sleepy` if you are working directly in the repository).
Run `make` and then enter `support/` folder and go through the practice items below.

Use the `tests/checker.sh` script to check your solutions.

```bash
./checker.sh
./tests/checker.sh
sleepy_creator ...................... passed ... 30
sleepy_creator_wait ................. passed ... 30
sleepy_creator_c .................... passed ... 40
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Wait for Me

Enter the `chapters/compute/processes/drills/tasks/wait-for-me-processes/` directory, run `make skels`, open the `support/src` folder and go through the practice items below.
Enter the `wait-for-me/` directory (or `chapters/compute/processes/drills/tasks/wait-for-me/` if you are working directly in the repository).
Run `make` and then enter `support/` folder and go through the practice items below.

Use the `tests/checker.sh` script to check your solutions.

Expand All @@ -10,7 +11,7 @@ wait_for_me_processes ...................... passed ... 100
```

1. Run the code in `wait_for_me_processes.py` (e.g: `python3 wait_for_me_processes.py`).
The parent process creates one child that writes and message to the given file.
The parent process creates one child that writes a message to the given file.
Then the parent reads that message.
Simple enough, right?
But running the code raises a `FileNotFoundError`.
Expand Down
2 changes: 1 addition & 1 deletion chapters/compute/processes/reading/processes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ student@os:~$ file /usr/bin/ls
```

When you run it, the `ls` binary stored **on the disk** at `/usr/bin/ls` is read by another application called the **loader**.
The loader spawns a **process** by copying some of the contents `/usr/bin/ls` in memory (such as the `.text`, `.rodata` and `.data` sections).
The loader spawns a **process** by copying some contents of `/usr/bin/ls` into memory (for example the `.text`, `.rodata` and `.data` sections).
Using `strace`, we can see the [`execve`](https://man7.org/linux/man-pages/man2/execve.2.html) system call:

```console
Expand Down
7 changes: 4 additions & 3 deletions chapters/compute/threads/drills/tasks/multithreaded/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Multithreaded

Enter the `chapters/compute/threads/drills/tasks/multithreaded/` folder, run `make skels`, and go through the practice items below in the `support/` directory.
Enter the `multithreaded/` directory (or `chapters/compute/threads/drills/tasks/multithreaded/` if you are working directly in the repository).
Run `make` and then enter `support/` folder and go through the practice items below.

1. Use the Makefile to compile `multithread.c`, run it and follow the instructions.
1. Use the Makefile to compile `multithreaded.c`, run it and follow the instructions.

The aim of this task is to familiarize you with the `pthreads` library.
In order to use it, you have to add `#include <pthread.h>` in `multithreaded.c` and `-lpthread` in the compiler options.
Expand All @@ -15,7 +16,7 @@ Enter the `chapters/compute/threads/drills/tasks/multithreaded/` folder, run `ma

Create a new function `sleep_wrapper2()` identical to `sleep_wrapper()` to organize your work.
So far, the `data` argument is unused (mind the `__unused` attribute), so that is your starting point.
You cannot change `sleep_wrapper2()` definition, since `pthreads_create()` expects a pointer to a function that receives a `void *` argument.
You must keep `sleep_wrapper2()`'s signature unchanged because `pthread_create()` requires a function of type `void *(*)(void *)`.
What you can and should do is to pass a pointer to a `int` as argument, and then cast `data` to `int *` inside `sleep_wrapper2()`.

**Note:** Do not simply pass `&i` as argument to the function.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Wait for It

The process that spawns all the others and subsequently calls `waitpid` to wait for them to finish can also get their return codes.
Update the code in `chapters/compute/threads/drills/tasks/sum-array-bugs/support/seg-fault/sum_array_processes.c` and modify the call to `waitpid` to obtain and investigate this return code.
Update the code in `sum-array-bugs/support/seg-fault/sum_array_processes.c` (or `chapters/compute/threads/drills/tasks/sum-array-bugs/support/seg-fault/sum_array_processes.c` if you are working directly in the repository) and modify the call to `waitpid` to obtain and investigate this return code.
Display an appropriate message if one of the child processes returns an error.

Remember to use the appropriate [macros](https://linux.die.net/man/2/waitpid) for handling the `status` variable that is modified by `waitpid()`, as it is a bit-field.
Expand All @@ -19,7 +19,7 @@ Thus, an application that uses processes can be more robust to errors than if it
## Memory Corruption

Because they share the same address space, threads run the risk of corrupting each other's data.
Take a look at the code in `sum-array-bugs/support/memory-corruption/python/`.
Take a look at the code in `sum-array-bugs/support/memory-corruption/python/` (or `chapters/compute/threads/drills/tasks/sum-array-bugs/support/memory-corruption/python/` if you are working directly in the repository).
The two programs only differ in how they spread their workload.
One uses threads while the other uses processes.

Expand Down
20 changes: 9 additions & 11 deletions chapters/compute/threads/drills/tasks/sum-array/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Libraries for Parallel Processing

In `chapters/compute/threads/drills/tasks/sum-array/support/c/sum_array_threads.c` we spawned threads "manually" by using the `pthread_create()` function.
Enter the `sum-array/` directory (or `chapters/compute/threads/drills/tasks/sum-array/` if you are working directly in the repository).
In `./support/c/sum_array_threads.c` we spawned threads "manually" by using the `pthread_create()` function.
This is **not** a syscall, but a wrapper over the common syscall used by both `fork()` (which is also not a syscall) and `pthread_create()`.

Still, `pthread_create()` is not yet a syscall.
Expand All @@ -10,15 +11,12 @@ Most programming languages provide a more advanced API for handling parallel com

## Array Sum in Python

Let's first probe this by implementing two parallel versions of the code in `sum-array/support/python/sum_array_sequential.py`.
One version should use threads and the other should use processes.
Run each of them using 1, 2, 4, and 8 threads / processes respectively and compare the running times.
Notice that the running times of the multithreaded implementation do not decrease.
This is because the GIL makes it so that those threads that you create essentially run sequentially.
First, let's navigate to the `sum-array/` directory (or `chapters/compute/threads/drills/tasks/sum-array/` if you are working directly in the repository).
Let's explore this by implementing two parallel versions of the sequential script located at `./support/python/sum_array_sequential.py`.
Create one version that uses threads and another that uses processes.

The GIL also makes it so that individual Python instructions are atomic.
Run the code in `chapters/compute/synchronization/drills/tasks/race-condition/support/python/race_condition.py`.
Every time, `var` will be 0 because the GIL doesn't allow the two threads to run in parallel and reach the critical section at the same time.
This means that the instructions `var += 1` and `var -= 1` become atomic.
After implementing them, run each version using 1, 2, 4, and 8 workers for both threads and processes and compare their execution times.

If you're having difficulties solving this exercise, go through [this](../../../guides/sum-array-threads.md) reading material.
Comment on lines -19 to -24
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part has been removed because it referenced an exercise from Lab 8. Keeping the lab materials separate will be less chaotic for students.

You will likely notice that the running time of the multi-threaded implementation does not decrease as you add more threads.
This is due to CPython's Global Interpreter Lock (GIL), which prevents multiple native threads from executing Python bytecode at the same time.
For this reason, CPU-bound tasks in Python do not typically see a performance increase from multi-threading.
18 changes: 9 additions & 9 deletions chapters/compute/threads/guides/sum-array-threads/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Sum array Threads
# Sum Array Threads

## Spreading the Work Among Other Threads

Expand All @@ -12,14 +12,14 @@ Therefore, they are more lightweight than processes.

## `std.parallelism` in D

D language's standard library exposes the [`std.parallelism`](https://dlang.org/phobos/std_parallelism.html), which provides a series of parallel processing functions.
The D language's standard library exposes the [`std.parallelism`](https://dlang.org/phobos/std_parallelism.html), which provides a series of parallel processing functions.
One such function is `reduce()`, which splits an array between a given number of threads and applies a given operation to these chunks.
In our case, the operation simply adds the elements to an accumulator: `a + b`.
Follow and run the code in `chapters/compute/threads/guides/sum-array-threads/support/d/sum_array_threads_reduce.d`.

The number of threads is used within a [`TaskPool`](https://dlang.org/phobos/std_parallelism.html#.TaskPool).
This structure is a thread manager (not scheduler).
It silently creates the number of threads we request and then `reduce()` spreads its workload between these threads.
This structure manages a pool of worker threads (not a scheduler).
It creates the requested number of worker threads, `reduce()` then spreads the workload between them.

Now that you've seen how parallelism works in D, go in `chapters/compute/threads/guides/sum-array-threads/support/java/SumArrayThreads.java` and follow the TODOs.
The code is similar to the one written in D, and it uses `ThreadPoolExecutor`.
Expand All @@ -31,20 +31,20 @@ javac SumArrayThreads.java
java SumArrayThreads 4
```

4 is the number of threads used, but you can replace the value with a number less or equal than your available cores.
4 is the number of threads used, but you can replace the value with a number less than or equal to your available cores.

## OpenMP for C

Unlike D, C does not support parallel computation by design.
It needs a library to do advanced things, like `reduce()` from D.
Unlike D, C does not provide built-in high-level parallel constructs.
Libraries such as OpenMP or pthreads provide parallelism.
We have chosen to use the OpenMP library for this.
Follow the code in `chapters/compute/threads/guides/sum-array-threads/support/c/sum_array_threads_openmp.c`.

The `#pragma` used in the code instructs the compiler to enable the `omp` module, and to parallelise the code.
In this case, we instruct the compiler to perform a reduce of the array, using the `+` operator, and to store the results in the `result` variable.
This reduction uses threads to calculate the sum, similar to `summ_array_threads.c`, but in a much more optimised form.
This reduction uses threads to calculate the sum, similar to `sum_array_threads.c`, but in a much more optimised form.

One of the advantages of OpenMP is that is relatively easy to use.
One of the advantages of OpenMP is that it is relatively easy to use.
The syntax requires only a few additional lines of code and compiler options, thus converting sequential code into parallel code quickly.
For example, using `#pragma omp parallel for`, a developer can parallelize a `for loop`, enabling iterations to run across multiple threads.

Expand Down
8 changes: 4 additions & 4 deletions chapters/compute/threads/guides/wait-for-me-threads/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ For now, do not wait for it to finish;
simply start it.

Compile the code and run the resulting executable several times.
See that the negative numbers appear from different indices.
This is precisely the nondeterminism that we talked about [in the previous section](tasks/wait-for-me-processes.md).
Note how the negative numbers appear at different indices on each run — this demonstrates the nondeterministic scheduling we discussed [in the previous section](tasks/wait-for-me-processes.md).

Now wait for that thread to finish and see that all the printed numbers are consistently negative.

As you can see, waiting is a very coarse form of synchronization.
If we only use waiting, we can expect no speedup as a result of parallelism, because one thread must finish completely before another can continue.
Waiting is a coarse form of synchronization.
If you start a thread and then immediately wait for it to finish before starting the next, you serialize the work and will see no speedup.
Finer-grained synchronization or letting threads run concurrently without sequential waits is needed to gain parallel speedup.
We will discuss more fine-grained synchronization mechanisms [later in this lab](reading/synchronization.md).

Also, at this point, you might be wondering why this exercise is written in D, while [the same exercise, but with processes](reading/processes.md) was written in Python.
Expand Down
1 change: 1 addition & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ lab_structure:
- title: Lab 6 - Multiprocess and Multithread
filename: lab6.md
content:
- reading/lab6.md
- tasks/sleepy.md
- tasks/wait-for-me-processes.md
- tasks/create-process.md
Expand Down