Атомарный доступ к данным на C++
Тип-контейнер состоит из фиксированного количества n_count
натуральных значений. Поддерживаются операции вычитания указанной величины из одного значения и добавления её к другому так, чтобы общая сумма всех значений в контейнере сохранилась. Вычитаемые и добавляемые величины контролируются так, чтобы все значения оставались неотрицательными.
Предполагается, что так часто, как возможно выполняются три задачи:
- поток, который выбирает 2 значения и усредняет их;
- поток, который выбирает 2 значения и произвольно перераспределяет их;
- метод, который отображает текущее состояние контейнера.
Смысл подобных задач - реализация на C++ атомарных операций. Сумма значений контейнера должна сохраняться, даже если два потока пытаются выполнить передачу одновременно, и простое решение состоит в том, чтобы гарантировать, что в любой момент времени действительно происходит только одна передача — то есть операция передачи является атомарной. Для этого в программе применяется массив мьютексов.
Пример выполнялся в консоли актуальной сборки Visual Studio 2019, комментарии к основным действиям есть в исходнике.
//У этой программы нет типового завершения, используйте кнопку "X" в окне консоли #include <algorithm> #include <array> #include <chrono> #include <iomanip> #include <iostream> #include <mutex> #include <random> #include <thread> constexpr int n_count = 2; //Поставьте здесь значение больше, например, 10 void equalizer(std::array<int, n_count>& values, std::array<std::mutex, n_count>& value_mutex) { //Работает в собственном потоке и случайным образом усредняет два значения std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> distribution (0, n_count - 1); while (true) { int from = distribution(gen); int to = distribution(gen); //2 индекса элементов if (from != to) { std::lock_guard<std::mutex> lock_first(value_mutex[std::min(from, to)]); std::lock_guard<std::mutex> lock_second(value_mutex[std::max(from, to)]); int diff = values[from] - values[to]; int amount = abs(diff / 2); //меняем на половину разницы между элементами if (diff < 0) std::swap(from, to); values[from] -= amount; values[to] += amount; } } } void randomizer(std::array<int, n_count>& values, std::array<std::mutex, n_count>& value_mutex) { //Работает в другомоптоке и перераспределяет значения между двумя элементами std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> distribution(0, n_count - 1); while (true) { int from = distribution(gen); int to = distribution(gen); //2 индекса элементов if (from != to) { std::lock_guard<std::mutex> lock_first(value_mutex[std::min(from, to)]); std::lock_guard<std::mutex> lock_second(value_mutex[std::max(from, to)]); //контролируем, что не меняем значение на большую величину, чем у нас есть std::uniform_int_distribution<> dist_amount(0, values[from]); int amount = dist_amount(gen); values[from] -= amount; values[to] += amount; } } } void print_values(const std::array<int, n_count>& values) { //Вывод значений из контейнера в консоль int total = 0; for (const int& value : values) { total += value; std::cout << std::setw(3) << value << ' '; } std::cout << "= " << std::setw(3) << total << std::endl; } int main() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dist(0, 9); //Поставьте здесь значение больше, например, (0, 99); std::array<int, n_count> values; //Наш контейнер std::array<std::mutex, n_count> value_mutex; //Мьютексы для контроля монопольности доступа потоков к элементам контейнера for (int& value : values) { //Случайно заполняем контейнер value = dist(gen); } print_values(values); //вывод начального состояния std::thread t_eq(equalizer, ref(values), std::ref(value_mutex)); std::thread t_rd(randomizer, ref(values), std::ref(value_mutex)); //инициализировали 2 потока while (true) { //бесконечный цикл, как и в потоках std::this_thread::sleep_for(std::chrono::seconds(1)); //пауза 1 сек. for (std::mutex& mutex : value_mutex) mutex.lock(); //блокировка потоков перед выводом в консоль print_values(values); //вывод очередного состояния for (std::mutex& mutex : value_mutex) mutex.unlock(); //разблокировка после вывода } return 0; }
30.08.2022, 23:36 [466 просмотров]