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 8e770dd commit ebada64
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 180 deletions.
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 ebada64

Please sign in to comment.