У меня есть вопрос. Я добавляю объект на карту и в потоке вызываю процедуру run () для всех элементов на карте. Я правильно понимаю, что в этом коде есть проблема синхронизации в процедуре процесса. Могу я добавить мьютекс? Учитывая, что эта процедура вызывается в ветке?

class Network {

public:
  Network() {
    std::cout << "Network constructor" << std::endl;
  }

  void NetworkInit(const std::string& par1) {
    this->par1 = par1;
  }

  ~Network() {
    std::cout << "Network destructor" << std::endl;
    my_map.clear();
  }

  void addLogic(uint32_t Id, std::shared_ptr<Logic> lgc) {
    std::lock_guard<std::mutex> lk(mutex);
    my_map.insert(std::pair<uint32_t, std::shared_ptr<Logic>>(Id, lgc));
    cv.notify_one();
  }

  void removeLogic(uint32_t Id) {
    std::unique_lock<std::mutex> lk(mutex);
    cv.wait(lk, [this]{return !my_map.empty(); });

    auto p = this->my_map.find(roomId);
    if (p != end(this->my_map)) {
      this->my_map.erase(roomId);
    }

    lk.unlock();
  }

 /**
   * Start thread
   */
  void StartThread(int id = 1) {
    running = true;
    first = std::thread([this, id] { process(id); });
    first.detach();
  }

  /**
   * Stop thread
   */
  void StopThread() {
    running = false;
  }

private:
  std::thread first;
  std::atomic<bool> running = ATOMIC_VAR_INIT(true);

  void process(int id) {
    while (running) {
      for (const auto& it:my_map) {
        it.second->run();
      }
      std::this_thread::sleep_for(10ms);
    }
  }    

 private:
  std::mutex mutex;
  std::condition_variable cv;

  using MyMapType = std::map<uint32_t, std::shared_ptr<Logic> >; 

  MyMapType my_map;
  std::string par1;
};
1
Max 13 Сен 2018 в 16:16

2 ответа

Лучший ответ

Первая идея - защитить map в целом с помощью мьютекса, который освобождается во время run. Это работает для addLogic, потому что вставка в map не делает недействительными итераторы, но не для deleteLogic, что может сделать недействительным само значение итератора, используемое process.

Здесь могут быть применимы более эффективные подходы без блокировки, такие как указатели опасности, но основная идея состоит в использовании отложенного списка удаления . Предполагая, что целью одновременного удаления является отмена задачи (а не просто очистка после завершения всей работы), разумно иметь потребительский поток для проверки непосредственно перед выполнением. Использование set (чтобы соответствовать вашему map) позволит списку удаления быть динамическим, и эти проверки будут эффективными.

Так что пусть другой mutex защитит список удаления и принимает его в начале каждой итерации в process:

  void addLogic(uint32_t Id, std::shared_ptr<Logic> lgc) {
    std::lock_guard<std::mutex> lk(mutex);
    my_map.insert(std::pair<uint32_t, std::shared_ptr<Logic>>(Id, lgc));
  }

  void removeLogic(uint32_t Id) {
    std::lock_guard<std::mutex> kg(kill_mutex);
    kill.insert(Id);
  }

private:
  std::set<uint32_t> kill;
  std::mutex mutex,kill_mutex;
  void process(int id) {
    for(;running;std::this_thread::sleep_for(10ms)) {
      std::unique_lock<std::mutex> lg(mutex);
      for(auto i=my_map.begin(),e=my_map.end();i!=e;) {
        if(std::lock_guard<std::mutex>(kill_mutex),kill.erase(i->first)) {
          i=my_map.erase(i);
          continue;    // test i!=e again
        }
        lg.unlock();
        i->second->run();
        lg.lock();
        ++i;
      }
    }
  }

В этом коде не используется использование condition_variable: нет необходимости ждать перед постановкой в очередь чего-либо для удаления.

1
Davis Herring 15 Сен 2018 в 16:19

Решение с низкоуровневыми примитивами параллелизма обычно не масштабируется и его нелегко поддерживать.

Лучшей альтернативой было бы иметь поточно-ориентированную «контрольную» очередь обновления карты или инструкций по завершению работника.

Что-то вроде этого:

enum Op {
        ADD,
        DROP,
        STOP
};

struct Request {
    Op op;
    uint32_t id;
    std::function<void()> action;
};

...

// the map which required protection in your code
std::map<uint32_t, std::function<void()>> subs;

// requests queue and its mutex (not very optimal, just to demonstrate the idea)
std::vector<Request> requests;
std::mutex mutex;

// the worker thread
std::thread worker([&](){
    // the temporary buffer where requests are drained to from the queue before processing
    decltype(requests) buffer;

    // the main loop
    while (true) {
        // requests collection (requires synchronization)
        {
            std::lock_guard<decltype(mutex)> const guard {mutex};
            buffer.swap(requests);
        }

        // requests processing
        for(auto&& request: buffer) {
            switch (request.op) {
                case ADD:
                    subs[request.id] = std::move(request.action);
                    break;
                case DROP:
                    subs.erase(request.id);
                    break;
                case STOP: goto endloop;
            }
        }

        // map iteration
        for (auto&& entry: subs) {
            entry.second();
        }
    }
    endloop:;
});
1
bobah 15 Сен 2018 в 17:14