LINUX.ORG.RU

Как найти зависший QMutex.

 , , ,


0

2

Есть класс для работы с девайсом, который помещается в QThread, в конструкторе класса объявлен таймер так:

timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(sl_timer()));
timer->start(0); //для наиболее частого опроса девайса 0 стоит.

sl_timer - это метод этого же класса, в котором идет опрос девайса. Этот метод и другие методы, которые вызываются из sl_timer и работают с глобальным QList защищены QMutex так:

QLockerMutex lock(&mutex); 
В одном из 5-10 случаев происходят какие-то зависания внутри sl_timer, полагаю что мьютекс где-то зависает. Как бы найти в какой конкретно функции это происходит ? Правильно ли я понимаю, что sl_timer из-за start(0) сможет вызваться второй раз еще когда первый вызов не отработал. То есть могут ли sl_timer тут начать работать параллельно и из-за локов мьютексами друг друга зависать ?

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

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

Мне нужно результаты работы этого класса кидать периодически в другой класс, для этого там таймеры заведены. Про таймер с 0 да, думаю это плохая идея, хочу поставить что-то типа 100 мс.

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

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

while(true){
опрашиваем датчик
emit sendData(Данные); это сигнал (ваш сигнал объявлен в секции signals:), в основном классе свяжите его со слотом приемником.
QThread::usleep(микросекунды);
}

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

А можешь объяснить как это сделать ? Я запускаю из qt под отладкой, как по потокам ее посмотреть ? Имелось ввиду ставить бряки и ловить где потоки в данный момент выполняются ? Или можно как-то получить из qt эту инфу по всем потокам сразу.

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

Если используешь QtCreator, то запусти приложение под отладкой, дождись пока оно зависнит и через меню Debug -> Interrupt останови выполнение. У тебя будет стек вызовов для всех потоков.

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

Дождаться пока подвиснет. Как в qtc сделать мне лень смотреть давно им не пользуюсь.

В gdb так(не забудь сделать отладочную сборку):

gdb -p <pid of process> /path/to/binary
thread apply all bt

В bt в котором увидишь слова lock или mutex - ищешь id потока. Например для вывода:

thread apply all bt
Thread 1 (process 8229):
#0  0x0000555555583db0 in main ()

id будет 1.

Дальше делаешь так:

thread <thread id>
up

Выполняешь up пока не поднимешься до «своего» кода.

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

А как с таймерами работать внутри класса, наследующегося от QObject и помещенного MoveToThread. Если втыкаю в слоте этого класса while(1); то таймер, который перед этим запускаю не срабатывает пока слот не отвиснет. Тимеры объявляю внутри этого класса так: timer= new QTimer(this); и делаю connect к слоту в этом классе.

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

man qeventloop

А лучше, сходи на лекториум и посмотри лекции по многопоточному программированию. Перед этим - можешь положить в голову мысль, что таймер это либо событие в очереди, либо условная переменная со сном по таймауту, либо их комбинация. Остальные случаи тебе пока рассматривать не нужно.

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik
QEventLoop loop;

work1::work1(QObject *parent) : QObject(parent)
{
    diagnosticDuration = 2000;


    _timer_Diagnostic = new QTimer(this);

    connect(_timer_Diagnostic,SIGNAL(timeout()),this,SLOT(sl_pr_DiagnosticDone()));

    _loop.exec();




}

void work1::sl_pr_DiagnosticDone()
{
    qDebug() << "sl_pr_DiagnosticDone";//сюда не приходит

}
void work1::sl_StartDiagnostic() //сюда по сигналу вызов идет
{
    qDebug() << "sl_StartDiagnostic";
    _timer_Diagnostic->start(diagnosticDuration);
	while(1)
    {
        QThread::msleep(100); //симуляция зависшего слота
    }

}

Вот не работает с ним, пробую так.

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

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

QEventLoop loop;
work1::work1(QObject *parent) : QObject(parent)
{
    diagnosticDuration = 2000;
    requestTimeout = 100;
	flag_stop_requst = false;

    _timer_Diagnostic = new QTimer(this);
    connect(_timer_Diagnostic,SIGNAL(timeout()),this,SLOT(sl_pr_DiagnosticDone()));

    _timer_Request = new QTimer(this);
    connect(_timer_Request,SIGNAL(timeout()),this,SLOT(sl_pr_RequestTimerTimeout()));

}


void work1::sl_pr_RequestTimerTimeout()
{

    qDebug() <<  "sl_pr_RequestTimerTimeout";//сюда попадает
   
   //тут долгая работа

    while(1)
    {
        QThread::msleep(100);
        if(flag_stop_requst)
	   break;
    }
    qDebug() << "fin while";


}


void work1::sl_pr_DiagnosticDone() //сюда не доходит из-за зависания слота sl_pr_RequestTimerTimeout
{
    qDebug() << "sl_pr_DiagnosticDone"; 
    flag_stop_requst = true;
    
    _timer_Diagnostic->stop();
    _timer_Request->stop();
    emit sig_DiagnosticDone();
}




void work1::sl_StartDiagnostic()
{
    qDebug() << "sl_StartDiagnostic";

    _timer_Diagnostic->start(diagnosticDuration); //этим таймером задаем время диагностики, по таймауту завершаем все
    _timer_Request->start(requestTimeout); //тут опрос девайса, 


    _loop.exec();
}

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

Так while(1) для симуляции ошибки зависания того слота. То есть ставить там _loop.processEvents(); это ведь испортит симуляцию с реальным случаем (да с ней работает, но идея была просто просимулировать зависший слот, чтобы по таймеру все грохнуть там). В реальной проге там виснет в каком-то методе на строке

QMutexLocker locker(&mutex);

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

Окей, с dealock я понял. Я теперь про общий случай, вот слот повис в каком-нибудь цикле из-за ошибки, пусть там не мьютекс, а просто из цикла не вышел. Как мне сделать на этот случай, чтобы таймеры отработали. Так как если они сработают, то главный класс получит инфу о проблеме и еще раз пошлет запрос так:

QTimer::singleShot(0,worker1,SLOT(sl_StartDiagnostic()));

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

Никак. Если поток повис, то тебе нужен другой. Но QObject не может быть в двух потоках одновременно. Разбирайся с мьютексами.

Если у тебя потоки блокируются на мьютексе, то QEventLoop тоже никак не поможет.

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

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

А как сделать защиту мьютексами если есть функции fun_1 ... fun_N, которые могут вызываться из других классов, они все с QList работают. Также есть fun_basic, которая тоже с этим QList работает и может вызывать fun_i разные. Как в этом случае их защищать, fun_i не вызывает fun_j.

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

А как сделать защиту мьютексами

Код, который работает с листом должен быть прикрыт мьютексом. Мы это обсудили в твоей предыдущей теме. Ничего нового.

Также есть fun_basic, которая тоже с этим QList работает и может вызывать fun_i разные.

Кажется, я догадываюсь почему у тебя потоки зависают. Ты делаешь так?

void fun_basic()
{
    QMutexLocker lock(mutex);
    // Здесь работаешь с листом
    
    // Вызов другого метода, который работает тоже с листом
    fun_i(); 
}

void fun_i()
{
    QMutexLocker lock(mutex); // <--- Опачки повторное взятие мьютекса
    // Здесь работаешь с листом
}

Если в подобном коде вызвать fun_basic, то будет вечная блокировка внутри fun_i.

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

А как защиту поставить на fun_i и на fun_basic, у меня могут быть вызовы из других классов типа

worker->fun_i();
а может быть вызов fun_i внутри своего класса из fun_basic. То есть мне нужно определять как-то откуда был вызов, чтобы принимать решение блочить или нет мьютексом. То есть если из fun_basic был вызов в fun_i, то я буду пропускать мьютекс. На fun_basic будет всегда мьютекс
QMutexLocker lock(mutex);
.

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

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

Можно передавать в fun_i булевый параметр, который будет говорить нужно ли брать блокировку. Только тут нужно быть аккуратней с QMutexLocker. Если он будет внутри блока if, то после выхода из этого блока блокировка снимется. Ещё стоит отметить, что если этот метод публичный, то это тоже архитектурное говно. Не надо рассчитывать, что внешний код знает особенности внутреннего устройства класса.

Можно разделить метод fun_i на два: fun_i и fun_i_private. fun_i будет брать блокировку и вызывать fun_i_private, в котором находится вся логика, но уже без блокировки. Соответственно, если fun_basic уже взял блокировку, то он может напрямую вызвать fun_i_private, а остальные будут работать через fun_i.

Короче раздели интерфейс своего класса на thread-safe и не thread-safe. А то так путаница возникает. Самому же сложно за всем следить.

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

QEventLoop::exec это то на чём блокируется QApplication::exec например, дальше учи матчасть. Например - возьми и напиши цикл обработки сообщений руками разок хотя бы.

Не занимайся херней, потрать время на нормальное вникание в тему, а не абы натыкать…

Вот этого товарища пару лекций послушай и упражнения выполни. Таненбаума про операционные системы хотя бы первые три главы осиль. Руками примитивы попиши, глянь сорцы QEventLoop и QApplication.

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

По usb подключен. Опрос идет максимально часто, так как нужно за определенное время собрать нужное число данных. Поэтому start(0) был таймер. Также требуется полученные данные выводить в gui раз в секунду. Для переброса в gui класс данные идут по сигналам. Вот как лучше организовать, чтобы максимально часто опрашивать и обновлять gui. Есть ли смысл в таких задачах для опроса делать отдельный консольный процесс, с которым обмен данными будет между основной программой для отображения данных в gui.

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

Нашел багу тут:

void Work2::setSensorValue()
{
    qDebug() << "wait setSensorValue";
    QMutexLocker locker(&mutex_1);
    qDebug() << "after mutex setSensorValue";

    for(int i=0;i<10;i++)
    {
        qDebug() << "try emit sig_TemperatureChanged" << i;
        emit sig_TemperatureChanged();
        qDebug() << "after emit sig_TemperatureChanged" << i;


    }
}

void Work2::clearAdcSpreading()
{
        qDebug() << "wait clearAdcSpreading";
        QMutexLocker locker(&mutex_1);
        qDebug() << "after mutex clearAdcSpreading";


}

У меня setSensorValue вызывалась. Сигнал sig_TemperatureChanged связан со слотом clearAdcSpreading. Висло на clearAdcSpreading, но я полагал, что сигналы просто встанут в очередь и после выхода из цикла в setSensorValue мьютех освободится, и все эти сигналы исполнятся. Но виснет сразу после первого. Можете объяснить почему именно так работает ? Сигнал надо было как метод рассматривать ?

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

Сигналы встанут в очередь только при Qt::queuedConnection. Ты когда делаешь connect явно пропиши, что хочешь: Qt::queuedConnection или Qt::directConnection. В последнем случае это всё равно что прямой вызов метода.

ox55ff ★★★ ()
Ответ на: комментарий от ox55ff
void MyWidget::setTemp(double temp)
{
	QMutexLocker lock(&mutex);
    Temperature = temp;
}

void MyWidget::setState(const QString &state)
{
	QMutexLocker lock(&mutex);
    Status = state;
}
void MyWidget::setList(QList<Myclass *> *list)
{
	QMutexLocker lock(&mutex);
    _list = list;
}

void MyWidget::paintEvent(QPaintEvent *)  //метод отрисовки
{

	QMutexLocker lock(&mutex);
	
	//чтение только _list, Status, Temperature
}

А нужно ли тут все эти методы защищать мьютексом от крэша или на setTemp упасть не сможет из-за double типа ? Методы setTemp, setState, setList вызываются другим тредом через сигналы

user2132 ()

просто скинь весь проект

если нет: как ты работаешь с девайсом: по serial порту или по tcp/ip? (да, не читал весь тред) если да: на винде или на лине? если на винде, если гора костылей, которые помогут.

всё это чисто касательно qt и qiodevice

напрямую связано с тем, как ты их юзаешь с сигналами/слотами/таймерами. особенно под виндой. там c Qt::CoarseTimer происходит полный ад и израиль

event loop не трогай ни в коем случае. тем более не дёргай qApp->processEvents() (никогда)

посмотри лучше на семафоры. имхо для твоей задачи они более актуальны. если лень, замени все QMutexLocker(…) на tryLock() { qt_noop(); } и лови свои говноподелки. но это не путь ниндзя.

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

Вот как лучше организовать, чтобы максимально часто опрашивать и обновлять gui.

я бы делал так:

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

2. gui класс подписан на события

мьютексы тут не нужны

x905 ★★★★★ ()