Skip to content

Commit

Permalink
Integrate atomic exercise as a final task in race exercise.
Browse files Browse the repository at this point in the history
  • Loading branch information
chavid authored and Sebastien Ponce committed Sep 30, 2024
1 parent 4b7b88d commit 717ec18
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 187 deletions.
1 change: 0 additions & 1 deletion exercises/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ endif()
# Include the exercises that (should) work on all platforms.
add_subdirectory( hello )
add_subdirectory( asan )
add_subdirectory( atomic )
add_subdirectory( basicTypes )
add_subdirectory( callgrind )
add_subdirectory( condition_variable )
Expand Down
8 changes: 2 additions & 6 deletions exercises/ExercisesCheatSheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,9 @@ Concurrency Exercises
Typical race condition where a simple mutex and lock_guard "solves" the problem.
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
The second step is to look at the execution time and find out that it's not really a solution.
### Atomicity (directory: [`atomic`](atomic))
Exactly the same race condition as above. Fix them using an `atomic<int>`.
*Optional*: Compare run times for lock and atomic solution. Those are likely not very different, as many locks are implemented using atomics.
Try then to use an `atomic<int>` to solve the issue and compare the execution time with the lock solution.
### Condition variables (directory: [`condition_variable`](condition_variable))
Expand Down
18 changes: 0 additions & 18 deletions exercises/atomic/CMakeLists.txt

This file was deleted.

14 changes: 0 additions & 14 deletions exercises/atomic/Makefile

This file was deleted.

13 changes: 0 additions & 13 deletions exercises/atomic/README.md

This file was deleted.

34 changes: 0 additions & 34 deletions exercises/atomic/atomic.cpp

This file was deleted.

34 changes: 0 additions & 34 deletions exercises/atomic/solution/atomic.sol.cpp

This file was deleted.

10 changes: 7 additions & 3 deletions exercises/race/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ add_executable( racing "racing.cpp" )
target_link_libraries( racing PRIVATE Threads::Threads )

# Create the "solution executable".
add_executable( racing.sol EXCLUDE_FROM_ALL "solution/racing.sol.cpp" )
target_link_libraries( racing.sol PRIVATE Threads::Threads )
add_dependencies( solution racing.sol )
add_executable( racing.sol1 EXCLUDE_FROM_ALL "solution/racing.sol1.cpp" )
target_link_libraries( racing.sol1 PRIVATE Threads::Threads )
add_dependencies( solution1 racing.sol1 )
add_executable( racing.sol2 EXCLUDE_FROM_ALL "solution/racing.sol2.cpp" )
target_link_libraries( racing.sol2 PRIVATE Threads::Threads )
add_dependencies( solution2 racing.sol2 )
add_dependencies( solution solution1 solution2 )
9 changes: 6 additions & 3 deletions exercises/race/Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
PROGRAM_NAME=racing

all: $(PROGRAM_NAME)
solution: $(PROGRAM_NAME).sol
solution: $(PROGRAM_NAME).sol1 $(PROGRAM_NAME).sol2


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

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

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

$(PROGRAM_NAME).sol2 : solution/$(PROGRAM_NAME).sol2.cpp
${CXX} ${CXXFLAGS} -g -std=c++17 -O2 -pthread -Wall -Wextra -L. -o $@ $<
20 changes: 12 additions & 8 deletions exercises/race/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@

## Instructions

* Compile and run the executable, see if it races
* If you have a bash shell, try `./run ./racing`, which keeps invoking the executable
until a race condition is detected
* (Optional) You can use `valgrind --tool=helgrind ./racing` to prove your assumption
* (Optional) If your operating system supports it, recompile with thread sanitizer.
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.

Tasks
- Compile and run the executable, check it races.
- If you have a bash shell, try `./run ./racing`, which keeps invoking the executable until a race condition is detected.
- (Optional) You can use `valgrind --tool=helgrind ./racing` to prove your assumption
- (Optional) If your operating system supports it, recompile with thread sanitizer.
With Makefile, use e.g. `make CXXFLAGS="-fsanitize=thread"`
* Use a mutex to fix the issue
* See the difference in execution time
* (Optional) Check again with `valgrind` or thread sanitizer if the problem is fixed
- Use a `std::mutex` to fix the issue.
- See the difference in execution time, for example with `time ./racing`.
You might have to increase `nRepeat` if it completes too fast, or lower it if it takes too long.
- (Optional) Check again with `valgrind` or thread sanitizer if the problem is fixed.
- Try to use `std::atomic` instead of the mutex, and compare the execution time.
21 changes: 12 additions & 9 deletions exercises/race/racing.cpp
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@

#include <iostream>
#include <thread>
#include <vector>

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

constexpr unsigned int nThread = 2;
constexpr std::size_t nThread = 10;
constexpr std::size_t nInc = 1000;
constexpr std::size_t nRepeat = 1000;

int main() {
int nError = 0;

for (int j = 0; j < 1000; j++) {
for (std::size_t j = 0; j < nRepeat; j++) {
int a = 0;

// Increment the variable a 100 times:
auto inc100 = [&a](){
for (int i = 0; i < 100; ++i) {
auto increment = [&a](){
for (std::size_t i = 0; i < nInc; ++i) {
a++;
}
};

// Start up all threads:
// Start up all threads
std::vector<std::thread> threads;
for (unsigned int i = 0; i < nThread; ++i) threads.emplace_back(inc100);
for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment);
for (auto & thread : threads) thread.join();

// Check
if (a != nThread * 100) {
if (a != nThread * nInc) {
std::cerr << "Race detected! Result: " << a << '\n';
nError++;
}
Expand Down
44 changes: 0 additions & 44 deletions exercises/race/solution/racing.sol.cpp

This file was deleted.

39 changes: 39 additions & 0 deletions exercises/race/solution/racing.sol1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>

constexpr std::size_t nThread = 10;
constexpr std::size_t nInc = 1000;
constexpr std::size_t nRepeat = 1000;

int main() {
int nError = 0;

for (std::size_t j = 0; j < nRepeat; j++) {
int a = 0;
std::mutex aMutex;

// Increment the variable a 100 times:
auto increment = [&a,&aMutex](){
for (std::size_t i = 0; i < nInc; ++i) {
std::scoped_lock lock{aMutex};
a++;
}
};

// Start up all threads:
std::vector<std::thread> threads;
for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment);
for (auto & thread : threads) thread.join();

// Check
if (a != nThread * nInc) {
std::cerr << "Race detected! Result: " << a << '\n';
nError++;
}
}

return nError;
}
37 changes: 37 additions & 0 deletions exercises/race/solution/racing.sol2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

#include <iostream>
#include <vector>
#include <thread>
#include <atomic>

constexpr std::size_t nThread = 10;
constexpr std::size_t nInc = 1000;
constexpr std::size_t nRepeat = 1000;

int main() {
int nError = 0;

for (std::size_t j = 0; j < nRepeat; j++) {
std::atomic<int> a{0};

// Increment the variable a 100 times:
auto increment = [&a](){
for (std::size_t i = 0; i < nInc; ++i) {
a++;
}
};

// Start up all threads
std::vector<std::thread> threads;
for (std::size_t i = 0; i < nThread; ++i) threads.emplace_back(increment);
for (auto & thread : threads) thread.join();

// Check
if (a != nThread * nInc) {
std::cerr << "Race detected! Result: " << a << '\n';
nError++;
}
}

return nError;
}

0 comments on commit 717ec18

Please sign in to comment.