Skip to content

Commit 0fc9226

Browse files
chavidSebastien Ponce
authored and
Sebastien Ponce
committed
Integrate atomic exercise as a final task in race exercise.
1 parent 4b7b88d commit 0fc9226

15 files changed

+115
-189
lines changed

exercises/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ endif()
1515
# Include the exercises that (should) work on all platforms.
1616
add_subdirectory( hello )
1717
add_subdirectory( asan )
18-
add_subdirectory( atomic )
1918
add_subdirectory( basicTypes )
2019
add_subdirectory( callgrind )
2120
add_subdirectory( condition_variable )

exercises/ExerciseSchedule_AdvancedCourse.md

-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ Day 3
4444

4545
### Race conditions (directory: [`race`](race), [cheatSheet](ExercisesCheatSheet.md#race-conditions-directory-race))
4646

47-
### Atomicity (directory: [`atomic`](atomic), [cheatSheet](ExercisesCheatSheet.md#atomicity-directory-atomic))
48-
4947
### Condition variables (directory: [`condition_variable`](condition_variable), [cheatSheet](ExercisesCheatSheet.md#condition-variables-directory-condition_variable))
5048

5149
### Generic programming / templates (directory: [`templates`](templates), [cheatSheet](ExercisesCheatSheet.md#generic-programming--templates-directory-templates))

exercises/ExercisesCheatSheet.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -344,13 +344,9 @@ Concurrency Exercises
344344
345345
Typical race condition where a simple mutex and lock_guard "solves" the problem.
346346
347-
The second step is to look at the execution time and find out that it's not really a solution. One could then try an atomic and see the difference, although I do not introduce them in the course
347+
The second step is to look at the execution time and find out that it's not really a solution.
348348
349-
### Atomicity (directory: [`atomic`](atomic))
350-
351-
Exactly the same race condition as above. Fix them using an `atomic<int>`.
352-
353-
*Optional*: Compare run times for lock and atomic solution. Those are likely not very different, as many locks are implemented using atomics.
349+
Try then to use an `atomic<int>` to solve the issue and compare the execution time with the lock solution.
354350
355351
### Condition variables (directory: [`condition_variable`](condition_variable))
356352

exercises/atomic/CMakeLists.txt

-18
This file was deleted.

exercises/atomic/Makefile

-14
This file was deleted.

exercises/atomic/README.md

-13
This file was deleted.

exercises/atomic/atomic.cpp

-34
This file was deleted.

exercises/atomic/solution/atomic.sol.cpp

-34
This file was deleted.

exercises/race/CMakeLists.txt

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ add_executable( racing "racing.cpp" )
1313
target_link_libraries( racing PRIVATE Threads::Threads )
1414

1515
# Create the "solution executable".
16-
add_executable( racing.sol EXCLUDE_FROM_ALL "solution/racing.sol.cpp" )
17-
target_link_libraries( racing.sol PRIVATE Threads::Threads )
18-
add_dependencies( solution racing.sol )
16+
add_executable( racing.sol1 EXCLUDE_FROM_ALL "solution/racing.sol1.cpp" )
17+
target_link_libraries( racing.sol1 PRIVATE Threads::Threads )
18+
add_dependencies( solution1 racing.sol1 )
19+
add_executable( racing.sol2 EXCLUDE_FROM_ALL "solution/racing.sol2.cpp" )
20+
target_link_libraries( racing.sol2 PRIVATE Threads::Threads )
21+
add_dependencies( solution2 racing.sol2 )
22+
add_dependencies( solution solution1 solution2 )

exercises/race/Makefile

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
PROGRAM_NAME=racing
22

33
all: $(PROGRAM_NAME)
4-
solution: $(PROGRAM_NAME).sol
4+
solution: $(PROGRAM_NAME).sol1 $(PROGRAM_NAME).sol2
55

66

77
clean:
8-
rm -f *o $(PROGRAM_NAME) *~ core $(PROGRAM_NAME).sol
8+
rm -f *o $(PROGRAM_NAME) *~ core $(PROGRAM_NAME).sol?
99

1010
$(PROGRAM_NAME) : $(PROGRAM_NAME).cpp
1111
${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $<
1212

13-
$(PROGRAM_NAME).sol : solution/$(PROGRAM_NAME).sol.cpp
13+
$(PROGRAM_NAME).sol1 : solution/$(PROGRAM_NAME).sol1.cpp
14+
${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $<
15+
16+
$(PROGRAM_NAME).sol2 : solution/$(PROGRAM_NAME).sol2.cpp
1417
${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $<

exercises/race/README.md

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11

22
## Instructions
33

4-
* Compile and run the executable, see if it races
5-
* If you have a bash shell, try `./run ./racing`, which keeps invoking the executable
6-
until a race condition is detected
7-
* (Optional) You can use `valgrind --tool=helgrind ./racing` to prove your assumption
8-
* (Optional) If your operating system supports it, recompile with thread sanitizer.
4+
The program `racing.cpp` is incrementing a shared integer many times, within several threads, which should lead to race conditions if no specific protection is used. The values of the global parameters `nThread`, `nInc` and `nRepeat` can be custommized for your own computer.
5+
6+
Tasks
7+
- Compile and run the executable, check it races.
8+
- If you have a bash shell, try `./run ./racing`, which keeps invoking the executable until a race condition is detected.
9+
- (Optional) You can use `valgrind --tool=helgrind ./racing` to prove your assumption
10+
- (Optional) If your operating system supports it, recompile with thread sanitizer.
911
With Makefile, use e.g. `make CXXFLAGS="-fsanitize=thread"`
10-
* Use a mutex to fix the issue
11-
* See the difference in execution time
12-
* (Optional) Check again with `valgrind` or thread sanitizer if the problem is fixed
12+
- Use a `std::mutex` to fix the issue.
13+
- See the difference in execution time, for example with `time ./racing`.
14+
You might have to increase `nRepeat` if it completes too fast, or lower it if it takes too long.
15+
- (Optional) Check again with `valgrind` or thread sanitizer if the problem is fixed.
16+
- Try to use `std::atomic` instead of the mutex, and compare the execution time.

exercises/race/racing.cpp

+12-9
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,40 @@
1+
12
#include <iostream>
23
#include <thread>
34
#include <vector>
45

56
/*
6-
* This program tries to increment an integer 100 times from multiple threads.
7-
* If the result comes out at 100*nThread, it stays silent, but it will print
7+
* This program tries to increment an integer `nInc` times in `nThread` threads.
8+
* If the result comes out at `nInc*nThread`, it stays silent, but it will print
89
* an error if a race condition is detected.
910
* If you don't see it racing, try ./run ./racing, which keeps invoking the
1011
* executable until a race condition is detected.
1112
*/
1213

13-
constexpr unsigned int nThread = 2;
14+
constexpr std::size_t nThread = 10;
15+
constexpr std::size_t nInc = 1000;
16+
constexpr std::size_t nRepeat = 1000;
1417

1518
int main() {
1619
int nError = 0;
1720

18-
for (int j = 0; j < 1000; j++) {
21+
for (std::size_t j = 0; j < nRepeat; j++) {
1922
int a = 0;
2023

2124
// Increment the variable a 100 times:
22-
auto inc100 = [&a](){
23-
for (int i = 0; i < 100; ++i) {
25+
auto increment = [&a](){
26+
for (std::size_t i = 0; i < nInc; ++i) {
2427
a++;
2528
}
2629
};
2730

28-
// Start up all threads:
31+
// Start up all threads
2932
std::vector<std::thread> threads;
30-
for (unsigned int i = 0; i < nThread; ++i) threads.emplace_back(inc100);
33+
for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment);
3134
for (auto & thread : threads) thread.join();
3235

3336
// Check
34-
if (a != nThread * 100) {
37+
if (a != nThread * nInc) {
3538
std::cerr << "Race detected! Result: " << a << '\n';
3639
nError++;
3740
}

exercises/race/solution/racing.sol.cpp

-44
This file was deleted.
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
#include <iostream>
3+
#include <vector>
4+
#include <thread>
5+
#include <mutex>
6+
7+
constexpr std::size_t nThread = 10;
8+
constexpr std::size_t nInc = 1000;
9+
constexpr std::size_t nRepeat = 1000;
10+
11+
int main() {
12+
int nError = 0;
13+
14+
for (std::size_t j = 0; j < nRepeat; j++) {
15+
int a = 0;
16+
std::mutex aMutex;
17+
18+
// Increment the variable a 100 times:
19+
auto increment = [&a,&aMutex](){
20+
for (std::size_t i = 0; i < nInc; ++i) {
21+
std::scoped_lock lock{aMutex};
22+
a++;
23+
}
24+
};
25+
26+
// Start up all threads:
27+
std::vector<std::thread> threads;
28+
for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment);
29+
for (auto & thread : threads) thread.join();
30+
31+
// Check
32+
if (a != nThread * nInc) {
33+
std::cerr << "Race detected! Result: " << a << '\n';
34+
nError++;
35+
}
36+
}
37+
38+
return nError;
39+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
#include <iostream>
3+
#include <vector>
4+
#include <thread>
5+
#include <atomic>
6+
7+
constexpr std::size_t nThread = 10;
8+
constexpr std::size_t nInc = 1000;
9+
constexpr std::size_t nRepeat = 1000;
10+
11+
int main() {
12+
int nError = 0;
13+
14+
for (std::size_t j = 0; j < nRepeat; j++) {
15+
std::atomic<int> a{0};
16+
17+
// Increment the variable a 100 times:
18+
auto increment = [&a](){
19+
for (std::size_t i = 0; i < nInc; ++i) {
20+
a++;
21+
}
22+
};
23+
24+
// Start up all threads
25+
std::vector<std::thread> threads;
26+
for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment);
27+
for (auto & thread : threads) thread.join();
28+
29+
// Check
30+
if (a != nThread * nInc) {
31+
std::cerr << "Race detected! Result: " << a << '\n';
32+
nError++;
33+
}
34+
}
35+
36+
return nError;
37+
}

0 commit comments

Comments
 (0)