Skip to content

Commit 05244fd

Browse files
committed
Upgrade concurrency module to real threads and thread-aware build scripts
1 parent 355a139 commit 05244fd

6 files changed

Lines changed: 148 additions & 78 deletions

File tree

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,49 @@
11
# Concurrency Basics
22

3-
This module introduces concurrency concepts: interleaving, shared-state hazards, and synchronization intent.
3+
This module introduces real multithreaded programming with `std::thread`.
44

55
## Quick Run
66

77
```bash
8-
g++ -std=c++17 -Wall -Wextra -pedantic example/main.cpp -o concurrency_basics_example
8+
g++ -std=c++17 -Wall -Wextra -pedantic -pthread example/main.cpp -o concurrency_basics_example
99
./concurrency_basics_example
1010
```
1111

12+
On Linux, `-pthread` is required for thread support.
13+
1214
## Topics Covered
1315

14-
- Task interleaving concept.
15-
- Race condition intuition.
16-
- Critical section idea.
17-
- Why mutexes/atomics are needed in real multithreaded code.
16+
- Creating and joining threads with `std::thread`.
17+
- Protecting shared state with `std::mutex` and `std::lock_guard`.
18+
- Coordinating producer/consumer workflows with `std::condition_variable`.
19+
- Preventing race conditions in shared data updates.
1820

1921
## Common Pitfalls
2022

21-
- Assuming shared updates are always safe.
22-
- Treating read-modify-write as atomic when it is not.
23-
- Ignoring ordering effects between operations.
23+
- Reading/writing shared state without synchronization.
24+
- Forgetting to join threads before program exit.
25+
- Waiting on condition variables without a predicate.
2426

2527
## Exercise Focus
2628

27-
- `exercises/01.cpp`: simulate parallel chunk sum with interleaved steps.
28-
- `exercises/02.cpp`: producer-consumer simulation with queue operations.
29+
- `exercises/01.cpp`: parallel chunk sum with worker threads.
30+
- `exercises/02.cpp`: producer-consumer queue with mutex and condition variable.
2931

3032
### Exercise Specs
3133

3234
1. `exercises/01.cpp`
33-
- Input: list of integers and chunk size.
34-
- Output: partial sums and final sum.
35-
- Edge cases: chunk size larger than list; empty list.
35+
- Input: list of integers and number of worker threads.
36+
- Output: per-thread partial sums and final total.
37+
- Edge cases: thread count larger than list size; non-positive thread count.
3638

3739
2. `exercises/02.cpp`
38-
- Input: sequence of produce/consume commands.
39-
- Output: queue state transitions.
40-
- Edge cases: consume on empty queue; multiple produce before consume.
40+
- Input: number of items to produce.
41+
- Output: produced/consumed item logs and completion summary.
42+
- Edge cases: zero items; consumer waiting while queue is empty.
4143

4244
## Checkpoint
4345

44-
- [ ] I can explain race conditions with examples.
45-
- [ ] I can identify critical sections in shared-state workflows.
46-
- [ ] I understand why synchronization primitives are required in real threads.
46+
- [ ] I can create and join worker threads safely.
47+
- [ ] I can protect critical sections with a mutex.
48+
- [ ] I can coordinate producers and consumers correctly.
4749
- [ ] I completed both exercises.
Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,36 @@
11
#include <iostream>
2+
#include <mutex>
3+
#include <thread>
24
#include <vector>
35

46
int main() {
5-
int sharedCounter = 0;
7+
const int threadCount = 4;
8+
const int incrementsPerThread = 50000;
69

7-
std::cout << "Simulated interleaving of two tasks:\n";
10+
int counter = 0;
11+
std::mutex counterMutex;
812

9-
std::cout << "Task A reads " << sharedCounter << '\n';
10-
int taskATemp = sharedCounter;
13+
auto worker = [&counter, &counterMutex, incrementsPerThread]() {
14+
for (int i = 0; i < incrementsPerThread; ++i) {
15+
std::lock_guard<std::mutex> lock(counterMutex);
16+
++counter;
17+
}
18+
};
1119

12-
std::cout << "Task B reads " << sharedCounter << '\n';
13-
int taskBTemp = sharedCounter;
20+
std::vector<std::thread> threads;
21+
threads.reserve(static_cast<std::size_t>(threadCount));
1422

15-
++taskATemp;
16-
++taskBTemp;
23+
for (int i = 0; i < threadCount; ++i) {
24+
threads.emplace_back(worker);
25+
}
1726

18-
sharedCounter = taskATemp;
19-
std::cout << "Task A writes " << sharedCounter << '\n';
27+
for (std::thread& thread : threads) {
28+
thread.join();
29+
}
2030

21-
sharedCounter = taskBTemp;
22-
std::cout << "Task B writes " << sharedCounter << '\n';
23-
24-
std::cout << "Final counter: " << sharedCounter << " (expected 2 in synchronized execution)\n";
31+
const int expected = threadCount * incrementsPerThread;
32+
std::cout << "Expected: " << expected << '\n';
33+
std::cout << "Actual: " << counter << '\n';
2534

2635
return 0;
2736
}
Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,68 @@
11
#include <iostream>
2+
#include <thread>
23
#include <vector>
34

45
int main() {
56
int n = 0;
6-
std::cout << "How many values? ";
7+
std::cout << "How many integers? ";
78
std::cin >> n;
89

910
if (n <= 0) {
10-
std::cout << "No data to sum.\n";
11+
std::cout << "Please enter a positive count.\n";
1112
return 0;
1213
}
1314

1415
std::vector<int> values;
1516
values.reserve(static_cast<std::size_t>(n));
16-
1717
for (int i = 0; i < n; ++i) {
1818
int value = 0;
1919
std::cin >> value;
2020
values.push_back(value);
2121
}
2222

23-
int chunkSize = 0;
24-
std::cout << "Chunk size: ";
25-
std::cin >> chunkSize;
23+
int workerCount = 0;
24+
std::cout << "How many worker threads? ";
25+
std::cin >> workerCount;
2626

27-
if (chunkSize <= 0) {
28-
std::cout << "Chunk size must be positive.\n";
27+
if (workerCount <= 0) {
28+
std::cout << "Worker count must be positive.\n";
2929
return 0;
3030
}
3131

32-
int total = 0;
33-
for (int start = 0; start < n; start += chunkSize) {
34-
int partial = 0;
35-
const int end = (start + chunkSize < n) ? (start + chunkSize) : n;
36-
for (int i = start; i < end; ++i) {
37-
partial += values[static_cast<std::size_t>(i)];
38-
}
39-
std::cout << "Partial sum [" << start << ", " << (end - 1) << "]: " << partial << '\n';
40-
total += partial;
32+
if (workerCount > n) {
33+
workerCount = n;
34+
}
35+
36+
std::vector<long long> partialSums(static_cast<std::size_t>(workerCount), 0);
37+
std::vector<std::thread> workers;
38+
workers.reserve(static_cast<std::size_t>(workerCount));
39+
40+
const int chunkSize = (n + workerCount - 1) / workerCount;
41+
42+
for (int workerIndex = 0; workerIndex < workerCount; ++workerIndex) {
43+
workers.emplace_back([&values, &partialSums, workerIndex, chunkSize, n]() {
44+
const int start = workerIndex * chunkSize;
45+
const int end = (start + chunkSize < n) ? (start + chunkSize) : n;
46+
47+
long long localSum = 0;
48+
for (int i = start; i < end; ++i) {
49+
localSum += values[static_cast<std::size_t>(i)];
50+
}
51+
52+
partialSums[static_cast<std::size_t>(workerIndex)] = localSum;
53+
});
54+
}
55+
56+
for (std::thread& worker : workers) {
57+
worker.join();
58+
}
59+
60+
long long total = 0;
61+
for (std::size_t i = 0; i < partialSums.size(); ++i) {
62+
std::cout << "Thread " << i << " partial sum: " << partialSums[i] << '\n';
63+
total += partialSums[i];
4164
}
4265

43-
std::cout << "Total sum: " << total << '\n';
66+
std::cout << "Final total: " << total << '\n';
4467
return 0;
4568
}
Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,61 @@
1+
#include <condition_variable>
12
#include <iostream>
3+
#include <mutex>
24
#include <queue>
3-
#include <string>
5+
#include <thread>
46

57
int main() {
6-
std::queue<int> q;
8+
int itemsToProduce = 0;
9+
std::cout << "How many items should be produced? ";
10+
std::cin >> itemsToProduce;
711

8-
std::cout << "Enter commands: produce <x>, consume, stop\n";
9-
while (true) {
10-
std::string command;
11-
std::cin >> command;
12+
if (itemsToProduce < 0) {
13+
std::cout << "Please enter a non-negative value.\n";
14+
return 0;
15+
}
16+
17+
std::queue<int> queue;
18+
std::mutex queueMutex;
19+
std::condition_variable queueCv;
20+
bool productionFinished = false;
21+
int consumedCount = 0;
22+
23+
std::thread producer([&]() {
24+
for (int i = 1; i <= itemsToProduce; ++i) {
25+
{
26+
std::lock_guard<std::mutex> lock(queueMutex);
27+
queue.push(i);
28+
std::cout << "Produced: " << i << '\n';
29+
}
30+
queueCv.notify_one();
31+
}
1232

13-
if (!std::cin) {
14-
break;
33+
{
34+
std::lock_guard<std::mutex> lock(queueMutex);
35+
productionFinished = true;
1536
}
37+
queueCv.notify_one();
38+
});
1639

17-
if (command == "produce") {
18-
int value = 0;
19-
std::cin >> value;
20-
q.push(value);
21-
std::cout << "Produced " << value << ", size=" << q.size() << '\n';
22-
} else if (command == "consume") {
23-
if (q.empty()) {
24-
std::cout << "Queue is empty.\n";
25-
} else {
26-
std::cout << "Consumed " << q.front() << '\n';
27-
q.pop();
40+
std::thread consumer([&]() {
41+
while (true) {
42+
std::unique_lock<std::mutex> lock(queueMutex);
43+
queueCv.wait(lock, [&]() { return productionFinished || !queue.empty(); });
44+
45+
if (queue.empty() && productionFinished) {
46+
break;
2847
}
29-
} else if (command == "stop") {
30-
break;
31-
} else {
32-
std::cout << "Unknown command.\n";
48+
49+
const int value = queue.front();
50+
queue.pop();
51+
++consumedCount;
52+
std::cout << "Consumed: " << value << '\n';
3353
}
34-
}
54+
});
55+
56+
producer.join();
57+
consumer.join();
3558

59+
std::cout << "Finished. Total consumed: " << consumedCount << '\n';
3660
return 0;
3761
}

scripts/build-all.ps1

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ if ($cppFiles.Count -eq 0) {
1212
$buildDir = Join-Path $root "build"
1313
New-Item -ItemType Directory -Force -Path $buildDir | Out-Null
1414

15+
$extraFlags = @()
16+
if ($IsLinux -or $IsMacOS) {
17+
$extraFlags += "-pthread"
18+
}
19+
1520
$index = 0
1621
foreach ($file in $cppFiles) {
1722
$output = Join-Path $buildDir ("check_" + $index)
1823
Write-Host "Compiling: $($file.FullName)"
19-
g++ -std=c++17 -Wall -Wextra -pedantic $file.FullName -o $output
24+
g++ -std=c++17 -Wall -Wextra -pedantic @extraFlags $file.FullName -o $output
2025
$index++
2126
}
2227

scripts/build-all.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@ fi
1313

1414
mkdir -p build
1515

16+
extra_flags=()
17+
case "$(uname -s)" in
18+
Linux*|Darwin*)
19+
extra_flags+=(-pthread)
20+
;;
21+
esac
22+
1623
index=0
1724
for file in "${files[@]}"; do
1825
output="build/check_${index}"
1926
echo "Compiling: $file"
20-
g++ -std=c++17 -Wall -Wextra -pedantic "$file" -o "$output"
27+
g++ -std=c++17 -Wall -Wextra -pedantic "${extra_flags[@]}" "$file" -o "$output"
2128
index=$((index + 1))
2229
done
2330

0 commit comments

Comments
 (0)