БлогNot. Атомарный доступ к данным на C++

Атомарный доступ к данным на 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 [399 просмотров]


теги: c++ программирование random

К этой статье пока нет комментариев, Ваш будет первым