LINUX.ORG.RU

std::bad_function_call на указатель метода класса

 


0

1

Приветствую.

В продолжении темы Вызов по указателю метода структуры вложенной в класс

Есть обработчик очереди класса в отдельном потоке

void CQueue::Handler(void)
{
    auto Get = [this](cmd_t & cmd) {
        std::unique_lock<std::mutex> ul(mtx);
        do {
            if (!abRun) return false;
        } while (!cv.wait_for(ul, std::chrono::seconds(1), [this]{ return !pq.empty(); }));

        cmd = pq.top();
        pq.pop();

        return true;
    };

    cmd_t cmd;
    while (Get(cmd))
        if (cmd.handle)
            (this->*(cmd.handle))(cmd.id, cmd.msg);
}

В саму очередь засовываю подобным образом из другого потока поступающих команд

pQ->Put(id, std::string(msg, msgLen), &CQueue::handle_pass);

частично описание класса

class CQueue
{
private:
    struct cmd_t {
        int8_t pri;
        std::string id, msg;
        void (CQueue::*handle)(const std::string & id, std::string & msg);
    };

    std::function<bool (const cmd_t &, const cmd_t &)> cmp = [](const cmd_t & l, const cmd_t & r){ return l.pri > r.pri; };

    std::priority_queue<cmd_t, std::vector<cmd_t>, decltype(cmp)> pq;

Все работает полагаю пока команды идут настолько «медленно», что CQueue::Handler успевает встать на условную переменную, но если удается за время вызова (this->(cmd.handle))(cmd.id, cmd.msg) засунуть в очередь несколько команд, то валится ошибка std::bad_function_call при вызове (this->(cmd.handle))

Разве нельзя одновременно передавать указатель на метод класса в другой поток и обращаться к нему???

★★★

Последнее исправление: wolverin (всего исправлений: 1)

Ответ на: комментарий от wolverin

)) из серии - не читал, но осуждаю (с) не может, я проверяю условие выхода из всех потоков abRun

по кускам кода сложно понять что происходит в целом

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

x905 ★★★★★
()
Ответ на: комментарий от alysnix

либо ее надо класть в голову очереди,

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

dvetutnev
()
Ответ на: комментарий от wolverin

Он один на все потоки

Флажок это нифига не упрощение, это усложнение. В схеме с сообщением-терминатором взаимодействие продюсера/потребителя производится только через один объект - очередь. С флажком их уже становится два. Обращение к ним идет из разных потоков, возможные состояние прослеживаются сложней (создание/удаление продюсеров/потребителей/очередей, вставка/извлечение в/из очереди, взаимодействие с флажком). Многопоточка вообще тем и сложна, что число возможных комбинацией (путей выполнения) стремиться в небеса. И часть из них может оказаться некорректными. Поэтому взаимодействия из разных потоков с разделяемыми объектами нужно минимизировать. Если не последуешь совету, то вангую, что очень скоро (уже) прибежишь сюда жаловаться про странные падения из-за нарушения временной безопасности.

Объекты и их взаимодействие.

Владеет очередью потребитель (ему же обрабатывать что там накопилось), продюсер держит ссылку на очередь. До того, как продюсер отправит сообщение-терминатор, очередь всегда жива, после этого он с ней не взаимодействует. При получение потребителем сообщения-терминатора он может спокойно удалить очередь без опаски, что кто-то в нее захочет записать.

Иные говорят, что безсуловное ожидание путь к вечной блокировке.

Не место тут для таймаутов. Для бизнес логики они в дальнейшем могут потребоваться, но маскировать ими кривой дизайн путь к вечной отладке.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 2)
Ответ на: комментарий от dvetutnev

в состоянии терминирования программа уже «все сообщения» обработать не может в общем случае.

у системы три базовых состояния

  • инициализации
  • нормальной работы
  • завершения.

в состоянии завершения система не может работать так же, как и в состоянии «нормальная работа».

пример:

тред А и В получают в очередь мессагу - останов, но не реагируют на нее, а продолжают выбирать предыдущие сообщения. тред A шлет мессагу треду В, но она оказывается после мессаги останов и не будет выбрана. тут возникает явное нарушение логики работы поскольку в нормальном режиме тред B должен был на нее среагировать. в общем случае это дает неопределенное состояние или поведение системы.

то есть, наилучшей стратегией будет мгновенное реагирование на сигнал останова и переход в состояние «завершения», явным образом разруливая всевозможные конфликты подсистем при завершении работы всей системы. потому что такие конфликты неизбежны.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

то есть, наилучшей стратегией будет мгновенное реагирование на сигнал останова и переход в состояние «завершения»

std::abort тогда уж. И не нужны никакие флажки.

тред А и В получают в очередь мессагу - останов, но не реагируют на нее, а продолжают выбирать предыдущие сообщения. тред A шлет мессагу треду В, но она оказывается после мессаги останов и не будет выбрана.

Я несколько раз прочитал и не понял этой конструкции) Давай по порядку. Есть два треда - A, B. Оба они получает мессагу. Оба в смысле каждому в отдельную очередь их помещают, или оба в смысле они вдвоем одну очередь поедают? После этого тред A зачем-то зачем-то отправляет треду B сообщение… В первом случаи попахивает нарушением слоев абстракции, во втором вообще какая-то синтетическая закольцованная муть получается.

dvetutnev
()
Ответ на: комментарий от dvetutnev

у тебя есть N тредов(с очередями). они неким образом обмениваются сообщениями. им все разом кидают сообщение - останов. причем в очередях у них еще есть необработанные сообщения.

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

возникает мутная ситуация которую надо разрулить. вот и думай как.

alysnix ★★★
()
Ответ на: комментарий от alysnix

им все разом кидают сообщение

работают, как будто все нормально, перекидывась сообщениями

Ну я и говорю, нарушение слоев абстракции, они не должны перекидываться между собой сообщениями если эти сообщения еще от куда-то приходят. Терминатор отправляется только один раз только одному потребителю. Если это не прозрачно, то потоки данных не формализованы. В линию обработку нужно выстраивать, а не плодить кучу непонятных состояний. Оно чем проще/прозрачней сделано, тем лучше. Тем более в многопоточке.

dvetutnev
()
Ответ на: комментарий от dvetutnev

это слишком сильное ограничение на архитектуру системы.

треды строго говоря про друг друга и не знают ничего. они выбирают какие-то каналы по uid из пула каналов, регят там свой канал, и все.

реагируют они только на сообщение себе в канал, разбрасывая сообщения в некие каналы, что они взяли из пула.

то есть архитектура «потоков данных» не определена строго или совсем.

строго определены только логики обработки сообщений в данном канале(тело обработчика) и набор сообщений и каналов, куда обработчик кидает свои мессаги.

такая вот абстрактная event-driven система.

ну например сеть подсистем типа p2p - они сами соединяются и сами обмениваются мессагами. и такую сеть надо правильно остановить.

alysnix ★★★
()
Ответ на: комментарий от alysnix

Раз event-driven и потоки данных фиксировать не хочется то нужно формализировать состояние акторов и переходы между состояними. В том числе и для граничных случаев, в которых часть системы уже ничего не принимает. Стула собственно два: делаем тупые обработчики и явно выстраиваем потоки данных, либо просто прицепляем обработчики к шине сообщений и делаем в них явные стейт-машины.

Первый вариант все равно делать придется, только на более высоком уровне.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от dvetutnev

я не очень понимаю какую задачу вы рассматриваете, а я рассматриваю задачу N процессов, обменивающихся сообщениями.

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

общего четкого стейта у такой системы нет, да он и не нужен, поскольку мало что дает.

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

Эммм, кажется мы не очень поняли друг-друга. Я подразумевал end-msg как отдельную сущность для каждого канала (очереди) связи. Это не запихивание броадкастом во все очереди одного сообщения-терминатора.

И да, это подразумевает заранее спроектированный бизнес-процесс shutdown.

dvetutnev
()
Ответ на: комментарий от wolverin

Про гоуто уже писал здесь ранее - это детские страхи дяди, которые остальные понесли как веру.

А зачем? Если что-то вроде:

while(pq.empty())
{
    if (!abRun) return false;
    cv.wait_for(ul, std::chrono::seconds(1));    
}

Читается и вообще выглядит намного проще.

Loki13 ★★★★★
()
Ответ на: комментарий от dvetutnev

Флажок это нифига не упрощение, это усложнение. В схеме с сообщением-терминатором взаимодействие продюсера/потребителя производится только через один объект - очередь.

Ну справедливости ради, если очередь FIFO, то придется обработать всю очередь пока завершимся(а это может быть и час ведь). И будет как в системд, что ждите выключения компьютера, там кто-то завершиться не может.

Loki13 ★★★★★
()
Ответ на: комментарий от dvetutnev

std::abort тогда уж. И не нужны никакие флажки.

Его же вроде нет в std? Мне помнится очень он нужен был, а его нет. Все говорят, что так нельзя, но как еще делать, когда зависло устройство синхронное на COM-порту? В венде(вендовых потоках) раньше можно было тупо абортнуть поток и хрен с ним что там потеряется, а как сделать это с std::thread?

Loki13 ★★★★★
()
Ответ на: комментарий от wolverin

Ну заверни всё в while(abRUN) и сверху if(pq.empty()) continue; Всё красивее выглядит, чем goto. Вот так вот:

while (abRun)
{
            if (pq.empty())
            {
                cv.wait_for(ul, std::chrono::seconds(1));
                continue;
            }

        cmd = pq.top();
        pq.pop();

        return true;
}
return false;
Loki13 ★★★★★
()
Последнее исправление: Loki13 (всего исправлений: 2)
Ответ на: комментарий от wolverin

Есть общепринятые подходы к написанию кода, когда сторонний человек сразу поймет что и зачем написано, а есть подходы с goto, когда придется прикладывать усилия мозговые, чтобы понять «а нахрена тут так наверчено?». Надо же стараться писать не write-only код, а чтобы твой последователь тебя не материл потом за странные велосипеды.

Loki13 ★★★★★
()
Ответ на: комментарий от Loki13

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

wolverin ★★★
() автор топика
Ответ на: комментарий от Loki13

не правильно выразился - я не переписываю чужой код, я с нуля переделываю, то что куплено в мою контору готовое и писано несколькими программистами.

вариант разобраться в глючном коде я изначально отбросил ))

wolverin ★★★
() автор топика