Re: Временный блокировщик
От: Кодт Россия  
Дата: 06.02.24 13:26
Оценка: 9 (2)
Здравствуйте, Marty, Вы писали:

M>Где-то в каком-то потоке периодически что-то производится, какие-то действия. Иногда надо временно запретить их производить.

M>В других потоках, или в том же потоке, можно запрещать, рекурсивно, главное, разлочить столько же раз, сколько и залочил.

Если не вдаваться в подробности, то правда ли, что апи получается такой? Назовём эту штуку "дверь". Потому что поток проходит через неё и дальше занимается своими делами.
struct Door {
  int level = 0;

  bool is_open() const { return level > 0; }

  // апи управляющей стороны (не ждёт) (меняет состояние двери)
  void open() { ++level; }
  void close() { --level; }

  // апи управляемой стороны (не меняет состояние двери)
  bool try_pass() const { return is_open(); }  // не ждёт
  bool pass(auto timeout) const { WAIT(timeout, is_open); return is_open(); }
  void pass() const { WAIT(is_open); }
}


Надо заметить, что поток может запереть сам себя:
void foo(Door& door) {
  door.close();
  .....
  door.pass();
  .....
}

и в этом случае ему нужна внешняя помощь — какой-то другой поток должен отпереть эту дверь

M>Что-то городить с какими-то примитивами синхронизации не вижу смысла, ну, может я ошибаюсь.


Примитивы синхронизации стоят дёшево, пока не попадают на блокировку.
Но если очень хочется упороться по лок-фри и написать своё, заведомо userspace, то ради бога.

Семафоры и мьютексы, будучи симметричными инструментами, тут не подойдут. (Не, конечно, на паре семафоров можно построить абсолютно все производные примитивы).
А вот на кондеях такое делается элементарно
class Door {
  int level = 0;
  mutable std::mutex guard;
  std::conditional_variable cv;

  bool is_open() const {
    return level > 0;
  }
  auto is_open_fun() const {
    return [this]() { return is_open(); };
  }

public:
  void open() {
    std::unique_lock lock(guard);
    ++level;  // спасибо vopl за фикс недочепятки
    if (is_open()) cv.notify_all();
  }
  void close() {
    std::unique_lock lock(guard);
    --level;
  }
    
  bool try_pass() const {
    std::unique_lock lock(guard);
    return is_open();
  }
  bool pass(auto timeout) const {
    std::unique_lock lock(guard);
    return cv.wait_for(lock, timeout, is_open_fun());
  }
  void pass() const {
    std::unique_lock lock(guard);
    cv.wait(lock, is_open_fun());
  }
};
Перекуём баги на фичи!
Отредактировано 06.02.2024 18:24 Кодт . Предыдущая версия . Еще …
Отредактировано 06.02.2024 18:24 Кодт . Предыдущая версия .
Отредактировано 06.02.2024 18:22 Кодт . Предыдущая версия .
Отредактировано 06.02.2024 13:48 Кодт . Предыдущая версия .
Re[2]: Временный блокировщик
От: reversecode google
Дата: 06.02.24 13:30
Оценка:
std::latch / std::barrier ?
Re[3]: Временный блокировщик
От: Кодт Россия  
Дата: 06.02.24 13:48
Оценка:
Здравствуйте, reversecode, Вы писали:

R>std::latch / std::barrier ?


Это примитивы для сетей Петри.
Прохождение через барьер всегда сбрасывает его состояние. Не факт, что нужно именно такое поведение.
Перекуём баги на фичи!
Re[4]: Временный блокировщик
От: Кодт Россия  
Дата: 06.02.24 13:54
Оценка:
Здравствуйте, vopl, Вы писали:

V>Ну, это если смотреть со стороны его реализации в конкретных схемах вытесняющего многопотока. Чуть абстрактнее если подняться, то семафор это "... счётчик, над которым можно производить две атомарные операции: увеличение и уменьшение значения на единицу, при этом операция уменьшения для нулевого значения счётчика является блокирующейся...". Вот по такой семантике у него вполне семафор, ноль как граница блокируемости, выше ноля свободно, ноль и ниже — заблокировано (или наоборот, не важно). Хотя, это все вопрос трактовки, спорить не стану, просто поясняю свой ход мыслей.


Семафор — счётчик расхода ресурсов. Его нельзя просто так отпереть так, что "работайте все кто хочет сколько хочет".
И его нельзя рекурсивно запереть так, что сам остался на свободе.

Ну и как строительный кирпич — семафор люто неудобная штука.
Перекуём баги на фичи!
Re[2]: Временный блокировщик
От: vopl Россия  
Дата: 06.02.24 13:57
Оценка: +1
Здравствуйте, Кодт, Вы писали:

К>А вот на кондеях такое делается элементарно

К>
К>class Door {
К>  int level = 0;
К>  mutable std::mutex guard;
К>  std::conditional_variable cv;

К>  bool is_open() const {
К>    return level > 0;
К>  }
К>  auto is_open_fun() const {
К>    return [this]() { return is_open();
К>  }

К>public:
К>  void open() {
К>    std::unique_lock lock(guard);
    // тут недочепятка, надо еще ++level; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
К>    if (is_open()) cv.notify_all();
К>  }
К>  void close() {
К>    std::unique_lock lock(guard);
К>    --level;
К>  }
    
К>  bool try_pass() const {
К>    std::unique_lock lock(guard);
К>    return is_open();
К>  }
К>  bool pass(auto timeout) const {
К>    std::unique_lock lock(guard);
К>    return cv.wait_for(lock, timeout, is_open_fun());
К>  }
К>  void pass() const {
К>    std::unique_lock lock(guard);
К>    cv.wait(lock, is_open_fun());
К>  }
К>};
К>
Re[3]: Временный блокировщик
От: Maniacal Россия  
Дата: 06.02.24 14:08
Оценка:
Здравствуйте, Marty, Вы писали:

M>Здравствуйте, reversecode, Вы писали:


R>>std::recursive_mutex не оно?


M>Он же рекурсивен только для захватившего потока, для остальных блокирует, не?


А данные в том потоке, в который иногда можно динамить, не те ли используются, что в других потоках? Не нужно ли лочить мьютекс в этом потоке так, чтобы другие подождали?
А так std::shared_mutex(C++17) или std::shared_timed_mutex(C++14). Для гурьбы потоков использовать lock_shared/unlock_shared, и try_lock для потока, который динамят
Но это не точно. Я такую разновидность мьютексов ещё не использовал. Что там с рекурсивностью — х.з.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.