mutex for child-threads?
От: turbocode  
Дата: 07.12.16 20:10
Оценка:
А есть mutex который умел бы для child-threads пропускать их в порядке очереди как по LOCK_GUARD?

Пример:
bool MyObject::build()
{
   RECURSIVE_LOCK_GUARD(); //захватывается recursive_mutex внешним потоком
   
   struct MyTask
   {
      MyAnotherObject* _another_obj;

      void Run(void* args)
      {
         CTaskArgs* _task_args = (CTaskArgs*)args;
         if (_task_args != NULL)
         {
            MyObject* _this = (MyObject*)_task_args->GetPointer("this");
            if (_this != NULL)
            {              
               _another_obj = new MyAnotherObject();
               _this->set_default_settings(_another_obj); //deadlock!!! Некрасиво выходит. Как правильно это исправить?
            };
         };
      };
   };

   MyTask my_task;
   CreateThreadPoolTask<MyTask, &MyTask::Run>(&my_task); //задача кидается на свободный поток из ThreadPool

   //...
};

bool MyObject::set_default_settings(MyAnotherObject* obj)
{
   RECURSIVE_LOCK_GUARD();

   //...
};
Отредактировано 07.12.2016 21:00 turbocode . Предыдущая версия . Еще …
Отредактировано 07.12.2016 20:46 turbocode . Предыдущая версия .
Re: mutex for child-threads?
От: Кодт Россия  
Дата: 07.12.16 23:25
Оценка:
Здравствуйте, turbocode, Вы писали:

T>А есть mutex который умел бы для child-threads пропускать их в порядке очереди как по LOCK_GUARD?


Если CreateThreadPoolTask синхронно выполняется (просто на стеке другого потока), то почему бы на время не разлочивать мьютекс в текущем потоке?
void create() {
  LOCK_GUARD(); // залочили

  Task task; // создали...
  {
    UNLOCK_GUARD(); // разлочили
    run(task);  // синхронно выполнили
  } // обратно залочили

  . . .
}


По подобной схеме работает ожидание условных переменных. Там мьютекс разлочивает и обратно залочивает сама процедура ожидания.
Хотя и бывают и другие сценарии выхода из критической секции "вглубь", а не "наружу-прочь".

Вообще, если предусмотреть синхронизацию на CV, то всё так и следует делать
condition cv;
mutex m;
bool done = false;

void create() {
  scoped_lock l(m);

  Task task;
  task.launch();  // запустили, но без ожидания - сразу дальше побежали
  .....
  while(!done) cv.wait(l);
  .....
}

void task_body() {
  scoped_lock l(m);
  // сюда мы попадём, очевидно, лишь после того, как родительский поток начнёт крутить цикл ожидания
  .....
  done = true;
  cv.notify_one();
}


Если потоков много, и шагов для передачи исключительного управления тоже, то возможны варианты. При известной ловкости рук хватит единственного мьютекса и единственного кондишена. Получится что-то типа Global Interpreter Lock.
А можно и гору мьютексов на каждое разделяемое данное и гору кондишенов на каждую точку рандеву понаделать.

И уж совсем "вообще", — критические секции потому и называются критическими, что выполняются в критическом режиме: быстро забежал, быстро сделал дело, быстро выбежал.
Лочить долгоиграющие процедуры — плохой тон и предпосылка, в лучшем случае, к инверсии приоритетов. А в худшем — да, к дедлокам.
Если надо, то — залочил — скопировал данные из-в разделяемый блок — разлочил — пошёл тупить дальше — снова залочил — обменял данные — снова разлочил.
Перекуём баги на фичи!
Re[2]: mutex for child-threads?
От: turbocode  
Дата: 07.12.16 23:42
Оценка:
Здравствуйте, Кодт, Вы писали:

Нужно чтобы для внешнего потока оставался recursive_mutex, но для внутренних его потоков recursive_mutex работал как обычный mutex.
Если делать unlock это же дыра получается, разве нет?
Отредактировано 07.12.2016 23:44 turbocode . Предыдущая версия .
Re[3]: mutex for child-threads?
От: Кодт Россия  
Дата: 07.12.16 23:59
Оценка: +1
Здравствуйте, turbocode, Вы писали:

T>Нужно чтобы для внешнего потока оставался recursive_mutex, но для внутренних его потоков recursive_mutex работал как обычный mutex.

T>Если делать unlock это же дыра получается, разве нет?

Если перед разлочиванием приводить общие данные в согласованное состояние, а после залочивания обратно понимать, что данные согласованы, но, возможно, изменены, — это не дыра.
Все кондишены делают это!

Что до внутренних и внешних потоков, — то напрашивается ввести два мьютекса.
Один лочится главным потоком и защищает публичные данные от посторонних.
Второй лочится главным и дочерними и предназначен для синхронизации доступа к потрохам.

Тем не менее, сама идея лочить данные надолго — это предпосылка к лётным происшествиям.
Нельзя ли как-то переделать программу, чтобы мьютексы защищали только критические секции, а не вообще всё сразу?
И переделать обмен данными между внешними потоками — вместо прямого доступа к общим данным, придумать какие-то очереди сообщений, почтовые ящики?
Перекуём баги на фичи!
Re[4]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 00:09
Оценка:
К>Что до внутренних и внешних потоков, — то напрашивается ввести два мьютекса.
К>Один лочится главным потоком и защищает публичные данные от посторонних.
К>Второй лочится главным и дочерними и предназначен для синхронизации доступа к потрохам.
Пример кода? Проблема в том что нельзя разделить на потроха и не потроха (то есть внутренний поток может вызвать у себя вполне полноценную внешнюю функцию)

К>Тем не менее, сама идея лочить данные надолго — это предпосылка к лётным происшествиям.

К>Нельзя ли как-то переделать программу, чтобы мьютексы защищали только критические секции, а не вообще всё сразу?
Данные хорошо разделены, поэтому коллизий не так уж и много судя по Concurrency Visualizer.

К>И переделать обмен данными между внешними потоками — вместо прямого доступа к общим данным, придумать какие-то очереди сообщений, почтовые ящики?

Нельзя. Это убьет перформанс в ноль.
Отредактировано 08.12.2016 8:40 turbocode . Предыдущая версия .
Re[4]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 08:31
Оценка:
К>Тем не менее, сама идея лочить данные надолго — это предпосылка к лётным происшествиям.

deadlock там не по этому, а потому что конфликт за ресурс между внешним и его внутренним потоком.
Re[5]: mutex for child-threads?
От: antropolog  
Дата: 08.12.16 08:54
Оценка: :)
Здравствуйте, turbocode, Вы писали:

T>Нельзя. Это убьет перформанс в ноль.


это твоя фантазия не подтверждённая фактическими данными.
Re[6]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 09:03
Оценка:
A>это твоя фантазия не подтверждённая фактическими данными.
у меня критический перформанс, пока думаю вынести из внутренних потоков зависимые от внешнего потока операции (потому что способа как их подружить между собой не вижу)
Re[5]: mutex for child-threads?
От: Кодт Россия  
Дата: 08.12.16 10:13
Оценка:
Здравствуйте, turbocode, Вы писали:

К>>Тем не менее, сама идея лочить данные надолго — это предпосылка к лётным происшествиям.


T>deadlock там не по этому, а потому что конфликт за ресурс между внешним и его внутренним потоком.


Если этот ресурс принадлежит только связке из внешнего и внутренних потоков, то выходов два:
— временная разлочка на процедурах ожидания — штатные кондишены или рукодельщина по мотивам кондишенов
— минимизация критических секций, копирование данных туда-оттуда и никаких ожиданий внутри секций

Если же на ресурс облизываются какие-то третьи, посторонние, потоки, и поэтому требуется надолго и превентивно лочить "для своих" | "чужими", отдельно решая задачу синхронизации между своими...
То я бы поискал в следующих направлениях
— делегировать право глобально лочить ресурс только одному своему потоку (естественный кандидат — это внешний поток)
— взять объект синхронизации, у которого две группы захватчиков — "свои" и "чужие" — это Read-Write-Lock
— написать свой объект TokenGuard, у которого может быть много групп захватчиков (различать их по токенам)
— вообще избавиться от залочивания

проблема с RWLock в том, что
— такое его использование — это хак, несоответствующий задуманной семантике: ведь "свои" собираются не читать, а менять ресурс
— своих может быть много, а чужой ровно один, то есть картина становится несимметричной, — посторонние потоки не могут провернуть тот же фокус с дочерними потоками

TokenGuard можно написать на кондишене (вообще, кондишены — очень сильное выразительное средство)
template<class Token = int> // тип токена, идентифицирующего группу - число, void*, GUID...
class TokenGuard {
private:
  optional<Token> token = nullopt; // идентификатор группы, которая сейчас захватила ресурс
                                   // вместо optional можно взять голый Token с выделенным нулевым значением

  unsigned count = 0;              // количество захватов на данный момент; 0 - ресурс свободен

  mutex mux;                       // ну и типовая обвязка нашего монитора
  condition cv;

public:
  void acquire(Token t) {
    scoped_lock lock(mux);

    // предусловия нет!

    while (count > 0 && token && token != t)  // пока ресурс захвачен чужими - ждём-кукуем
      cv.wait(lock);
    assert((count == 0 && !token    ) ||  // либо ресурс не захвачен никем
           (count >  0 && token == t));   // либо захвачен "нашими"

    count++;
    if (!token) {  // разбудим всех, порадуем коллег
      token = t;
      cv.notify_all();
    }

    assert(count > 0 && token && token == t);  // постусловие: ресурс должен быть захвачен нашими
  }

  void release(Token t) {
    scoped_lock lock(mux);

    assert(count > 0 && token && token == t);  // предусловие: ресурс должен быть захвачен нашими

    count--;
    if (count == 0) {  // это была последняя разлочка - разбудим всех
      token = nullopt;
      cv.notify_all();
    }

    // постусловие:
    assert((count == 0 && !token    ) ||  // либо ресурс больше не захвачен никем
           (count >  0 && token == t));   // либо всё ещё захвачен "нашими"
  }
};

Можно ли здесь обойтись без ложных пробуждений — и потенциальных проблем, связанных с ними, — в первую очередь, голодания? Наверно, можно, но это будет довольно громоздкий код.

Ну а scoped lock для него — элементарный.
template<class Token>
class ScopedTokenLock {
  TokenGuard<Token>* guard_;
  Token token_;
public:
  explicit ScopedTokenLock(TokenGuard<Token>& guard, Token t)
      : guard_(&guard),
        token_(token) {
    guard_->acquire(token_);
  }
  ~ScopedTokenLock() {
    guard_->release(token_);
  }

  ScopedTokenLock(ScopedTokenLock&&) = delete;
  ScopedTokenLock(const ScopedTokenLock&&) = delete;
  void operator = (ScopedTokenLock&&) = delete;
  void operator = (const ScopedTokenLock&&) = delete;
};
Перекуём баги на фичи!
Re[6]: mutex for child-threads?
От: Кодт Россия  
Дата: 08.12.16 10:24
Оценка:
Здравствуйте, Кодт, Вы писали:

забыл написать пример применения:
// общие данные
SomeSharedResource;
TokenGuard<int> group_guard;
mutex perfect_mutex;

// обвязка моей группы
int my_token = 123;

void my_thread_1() {
  ScopedTokenLock<int> gl(group_guard, my_token); // с этого момента только мои потоки будут лазить к ресурсу

  {
    unique_lock ml(perfect_mutex);  // в этой критической секции - только этот поток (а не вся группа) лазит
    . . .
  }

  {
    unique_lock ml(perfect_mutex);
    . . .
    while(!some_condition) some_cv.wait(ml);  // временно разлочил, позволив другим потокам группы сделать что-то полезное
    . . .
  }
}

void my_thread_2() {
  ScopedTokenLock<int> gl(group_guard, my_token);

  unique_lock ml(perfect_mutex);
  . . .
  some_condition = true;
}

// аналогично - другая группа

int their_token = 456;
.....
Перекуём баги на фичи!
Re[6]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 10:28
Оценка:
К>- написать свой объект TokenGuard, у которого может быть много групп захватчиков (различать их по токенам)
Думал об этом но это ведет к непрозрачному коду и теряется универсальность;

К>- вообще избавиться от залочивания

да, этот вариант мне больше всего нравится.
Re[7]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 10:35
Оценка:
Здравствуйте, Кодт, Вы писали:

Спасибо, я спрашивал в надежде что возможно есть какой то простой универсальный паттерн для этого случая которого я не знаю, но мои надежды не оправдались.
Re[7]: mutex for child-threads?
От: antropolog  
Дата: 08.12.16 10:45
Оценка:
Здравствуйте, turbocode, Вы писали:

A>>это твоя фантазия не подтверждённая фактическими данными.

T>у меня критический перформанс, пока думаю вынести из внутренних потоков зависимые от внешнего потока операции (потому что способа как их подружить между собой не вижу)

мне просто видится что говорить о том что очередь это "медленно" используя стандартный аллокатор ( new MyAnotherObject() ), строки как ключи (GetPointer("this")) и тредпул (который очередь чуть менее чем полностью ) не говоря уже о мьютексах, защищающих что-то большее чем своп двух указателей — несколько преждевременно.

Насчёт мьютексов:
В твоей схеме есть вариант это иметь два типа объектов — с блокировкой и без. Те что без используются фабрикой. Наружу отдаётся объект, который оборачивает объект без мьютексов и проксирует вызовы используя блокировку.
Re[8]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 10:55
Оценка:
A>Насчёт мьютексов:
A>В твоей схеме есть вариант это иметь два типа объектов — с блокировкой и без. Те что без используются фабрикой. Наружу отдаётся объект, который оборачивает объект без мьютексов и проксирует вызовы используя блокировку.

Блокировка должна быть как от внешних потоков так и от внутренних потоков, только для внутренних блокировка как бы помягче должна быть. Но для этого mutex должен как то автоматически различать внешний поток от внутреннего и менять свое поведение в runtime.
Re[8]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 11:17
Оценка:
A>используя стандартный аллокатор ( new MyAnotherObject() ), строки как ключи (GetPointer("this")) и тредпул (который очередь чуть менее чем полностью ) не говоря уже о мьютексах, защищающих что-то большее чем своп двух указателей — несколько преждевременно.
Это экономия на спичках потому что в конце все равно нужно ждать результата от самого медленного потока.
То есть на более правильной балансировке нагрузки можно выиграть больше времени чем от замены строкового ключа на целый тип идентификатора.
Re[8]: mutex for child-threads?
От: Кодт Россия  
Дата: 08.12.16 13:45
Оценка:
Здравствуйте, turbocode, Вы писали:

T>Спасибо, я спрашивал в надежде что возможно есть какой то простой универсальный паттерн для этого случая которого я не знаю, но мои надежды не оправдались.


Универсальный паттерн я показал — это обобщение мьютекса.
Известны два вида реентерабельных мьютексов — это собственно мьютекс (реентерабельный по идентификатору потока) и RW-Lock (реентерабельный по роли читателя).
Почему бы не изобрести третий вид.
Идейно это близко к апартаментам, но не совсем.

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

Но это всё попытки помочь тебе наугад.
Под конкретный сценарий работы может потребоваться что-то совсем другое.

Если, скажем, выполняется следующая работа:
— некий объект открывает сокет, запрещая другим объектам работать с ним
— изо всех стволов (но соблюдая очерёдность) пишет-читает туда
— закрывает сокет
то, возможно, хватит средств операционной системы.
Получаются два ресурса
— собственно сокет (по имени-адресу-порту...) — который лочится прямо функциями открытия-закрытия на всё время сессии
— хэндл сокета, полученный при открытии — который лочится, как обычно, пользовательским мьютексом на время каждой законченной порции чтения-записи
Перекуём баги на фичи!
Re[9]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 19:16
Оценка:
К>Если, скажем, выполняется следующая работа:
К>- некий объект открывает сокет, запрещая другим объектам работать с ним
К>- изо всех стволов (но соблюдая очерёдность) пишет-читает туда

На очередность мне плевать.

К>- закрывает сокет


К>то, возможно, хватит средств операционной системы.

К>Получаются два ресурса
К>- собственно сокет (по имени-адресу-порту...) — который лочится прямо функциями открытия-закрытия на всё время сессии
сессия это внешний поток по сути.

К>- хэндл сокета, полученный при открытии — который лочится, как обычно, пользовательским мьютексом на время каждой законченной порции чтения-записи

а это внутренние потоки, но они тоже должны между собой быть синхронизированы если обращаются к общему ресурсу внутри сессии.
Re[10]: mutex for child-threads?
От: Кодт Россия  
Дата: 08.12.16 20:02
Оценка:
Здравствуйте, turbocode, Вы писали:

К>>Если, скажем, выполняется следующая работа:

К>>- некий объект открывает сокет, запрещая другим объектам работать с ним
К>>- изо всех стволов (но соблюдая очерёдность) пишет-читает туда

T>На очередность мне плевать.


Соблюдая очерёдность — это значит, что не пытаются вдвоём в один и тот же момент в сокет кричать.

К>>Получаются два ресурса

К>>- собственно сокет (по имени-адресу-порту...) — который лочится прямо функциями открытия-закрытия на всё время сессии
T>сессия это внешний поток по сути.

так я угадал про сокет?
или мы продолжаем играть в переабстрагирование?
Перекуём баги на фичи!
Re[11]: mutex for child-threads?
От: turbocode  
Дата: 08.12.16 20:14
Оценка:
К>так я угадал про сокет?
Задача не связана с сокетами

К>или мы продолжаем играть в переабстрагирование?

Объяснил как эта задача выглядела бы на сокете. Единственное различие что мой "сокет" сам знает что нужно сделать у себя внутри после открытия сессии внешним потоком (то есть извне не нужно ничего писать и читать) внешний поток всего лишь должен дождаться всех результатов от внутренних потоков и закрыть сессию.
Re[12]: mutex for child-threads?
От: Кодт Россия  
Дата: 08.12.16 22:42
Оценка:
Здравствуйте, turbocode, Вы писали:

T>Объяснил как эта задача выглядела бы на сокете. Единственное различие что мой "сокет" сам знает что нужно сделать у себя внутри после открытия сессии внешним потоком (то есть извне не нужно ничего писать и читать) внешний поток всего лишь должен дождаться всех результатов от внутренних потоков и закрыть сессию.


Ну так это задача очень хорошо известная и простая: пересечение барьера.
Решается одним из способов:
— сделать join всех внутренних потоков (если результат совпадает с моментом завершения потока; это плохо вяжется с пулом потоков, но для общей картины упомяну)
— завести семафор, и главный поток должен попытаться опустить его n раз по числу ожидаемых результатов, а внутренние потоки поднять его 1 раз каждый (в стандартной библиотеке семафора нет, а в позиксе, винапи и вроде бы в бусте — есть, но сделать семафор на кондишене — раз плюнуть)
condition cv;
mutex m;
int count = 0;

void acquire(int c) {
  unique_lock l(m);
  while (count < m) cv.wait(l);
  count -= m;
}
void release(int c) {
  unique_lock l(m);
  count += m;
  if (count >= 0) cv.notify_all();
}


— завести n обещаний (std::promise), дочерние потоки выполнят обещания, а главный поток дождётся фьючерсов (std::future) этих обещаний
Ну например
vector<promise<Result>> promises;

void main_thread() {
  promises.resize(n);

  // запускаем потоки в пуле
  .....

  // собираем результаты
  vector<Result> results;
  for(auto& p : promises) results.push_back(p.get_future().get()); // get у фьючерса висит в ожидании
  // что-то с этими результатами делаем
}

void inner_thread(int i) {
  .....
  .....
  .....
  promises[i].set_value(someResult);
}
Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.