Вопрос по qdebug, qt, c++, multithreading – Является ли qDebug () поточно-ориентированным?

24

ЯвляетсяqDebug() потокобезопасный? Под потокобезопасным я имею в виду не просто сбой, но и если я позвонюqDebug() из разных потоков возможно ли смешивание вывода? Я проверил его с помощью этого кода, и, похоже, это не так, однако я не смог найти нигде в документации, где они говорят об этом.

Это мой тестовый код:

#include <QtConcurrent>
#include <QApplication>
void print_a() {
    for (int ii = 0; ii < 10000; ii++) {
        qDebug("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
    }
}
void print_b()
{
    for (int ii = 0; ii < 10000; ii++) {
        qDebug("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
    }
}
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QtConcurrent::run(print_a);
    QtConcurrent::run(print_b);
    return a.exec();
}

Былиникакие 'a' и 'b' не смешаны в одной строке в любом месте, но я все еще не уверен, что это 100% потокобезопасный ...

Я разместил свой комментарий в качестве ответа. thuga
Документы говорятЕсли функция не помечена как поточно-ориентированная или реентерабельная, ее не следует использовать из разных потоков, В случаеqDebug() он не говорит, что он потокобезопасен, поэтому его, вероятно, небезопасно использовать из разных потоков. thuga
@thuga Это правильный ответ на мой вопрос, тогда вы должны опубликовать его :) sashoalm

Ваш Ответ

7   ответов
8

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

aaaaaaaaaaaabbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbabbbbbbbbbbbbbbbbbb

Мне повезло сqDebug() << "..."

Протестировано в Qt5.2.1 с компилятором mingw48_32.

проголосовал, потому что я мог подтвердить это с помощью Qt 5.3.1 (MinGW 4.8.2 32bit) mozzbozz
12

Если функция не помечена как поточно-ориентированная или реентерабельная, ее не следует использовать из разных потоков, В случаеqDebug() это говорит:Note: This function is thread-safe.

(этот ответ был обновлен ... в документах не указывалось, что функция была поточно-ориентированной.)

Подождите, я на самом деле просто подумал об этом еще немного, и безопасность потока подразумевает использование одного и того же объекта, верно? НоqDebug() << "foo"; строит новыйQDebug объект, поэтому мы будем говорить о повторном входе, на самом деле, а не потокобезопасности. Извините, я не упомянул об этом раньше, но я понял это сейчас. sashoalm
@sashoalm Он также не помечен как реентерабельный. thuga
Да, ноQRect а такжеQPoint тоже нет. Я разместил новый вопрос, конкретно о проблеме повторного входа, вы можете увидеть его наstackoverflow.com/questions/22535094/... sashoalm
2

qDebug( ..text.. ) Потокобезопасен (по крайней мере, если скомпилирован с GCC).

Если вы посмотрите в исходный файл qt (4)qglobal.cpp, qDebug звонкиqt_message_output какие звонкиfprintf(stderr, ...), который является потокобезопасным в glibc

(qDebug() << .. это другая история)

@mozzbozz: Возможно, это было изменено в qt5? Btw,#include <QtConcurrentRun>  было правильно в источнике.QtConcurrent не существует в qt4 BeniBela
@BeniBela: Ну, это может быть причиной для разных ответов. Тем не менее, можно сказать, что «никогда не полагайтесь на функцию, не гарантированную документами» ... Хорошо, хорошо#include <QtConcurrentRun> - не могу найти ничего об этом на Google в момент, когда я отредактировал сообщение ... хм,документы сказать,QtConcurrent по крайней мере доступен с Qt 4.4. Так что я думаю, это лучший тестовый код, потому что он совместим со всеми «текущими» версиями Qt (я думаю, что никто не использует <Qt 4.4 больше? OO) mozzbozz
О, это так. Кажется, я запомнил это неправильно BeniBela
Я отправилвопрос про fprintf для MSVCRT также, так как я использую Qt на Windows. sashoalm
0

И то и другое

qDebug("xx")

так же как

qDebug() << "xx"

qInfo, qWarning, qCritical и классифицированные версии, такие как qCDebug, qCInfo, qCWarning, qCritical, безопасны для одновременного использования из разных потоков.

Однако вы должны убедиться, что приемник журналов может также обрабатывать большие данные атомарно. Отсюда и путаница, потому что stderr явно ломает слишком длинную строку. Вы можете легко проверить это, просто заменив qDebug () на fprintf (stderr) в примере: он показывает точно такое же поведение для меня.

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

1

то, что делают функции, связанные с QtDebug (такие как qDebug (), qWarning () и т. Д.), Это обработчик сообщения вызова, который может быть установлен вызовом qInstallMessageHandler (). Так что вам решать - будет ли этот обработчик сообщений поточно-ориентированным или нет. Существует реализация по умолчанию, которая просто печатает сообщения в stderr, она не предотвращает смешанный вывод из разных потоков, поэтому, если вы хотите иметь всегда несмешанный построчный вывод для ваших сообщений отладки, предупреждений и ошибок из разных потоки, вы должны установить свой собственный обработчик с некоторой блокировкой (такой как QMutex), например так:

QMutex messageMutex;

void myMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QMutexLocker locker(&messageMutex);

    [print msg and context somewhere]
}

int main(int argc, char **argv)
{
    qInstallMessageHandler(myMessageHandler);

    QApplication app(argc, argv);

    [...]
}

НОТА: какКуба Обер правильно отмечено, это следует использовать с осторожностью (как и в случае любой блокировки в целом, хотя). Например, вы можете получить взаимоблокировку, если функции QtDebug вызываются из внутренних компонентов библиотеки ввода-вывода при использовании той же библиотеки ввода-вывода для вывода сообщений отладки (это возможно, например, когда обработчик сообщений QtDebug вызывает ввод-вывод при удержании блокировки на нерекурсивном мьютексе базовый механизм ввода-вывода вызывает некоторую функцию обратного вызова, а затем эта функция обратного вызова вызывает функцию QtDebug, которая снова вызывает тот же обработчик).

Следует отметить, чтоэто возможно ужасная идея, Зачем? Потому что, если вы в конечном итоге вызовете обработчик сообщений, в то время как другие мьютексы удерживаются, вы можете оказаться в тупике, поскольку нет гарантии, что мьютексы будут получены в порядке, необходимом для предотвращения взаимных блокировок. Например, если стандартный вывод используется где-либо еще в вашем коде, библиотека C может содержать собственный мьютекс и т. Д. Требуется большая осторожность, чтобы этот код не зашел в тупик. Это возможно, но не думайте, что вы можете сделать это, и это будет "просто работать". Kuba Ober
Вы можете получить взаимоблокировку, если функции QDebug вызываются из некоторых внутренних компонентов библиотеки ввода-вывода при использовании одной и той же библиотеки ввода-вывода для печати отладочных сообщений, это правда. Так что это следует использовать с осторожностью. Я добавил соответствующую заметку к своему ответу. Oleg Derevenetz
2

http://www.qtcentre.org/threads/28879-redirecting-qDebug-to-file-threading-question

Цитирование:

Чтобы ответить на вопрос, является ли qdebug потокобезопасным: QDebug использует QTextstream. QTextStream не является потокобезопасным. В документации не ясно об этом, но если вы посмотрите на исходный код qdebug или qtextstream, то увидите, что в коде вообще нет блокировки мьютекса.

Будьте осторожны - выходные потоки могут быть буферизованы. Буферы могут быть внутренне синхронизированы (и часто таковыми являются!), Но целые механизмы не должны быть. Попробуйте сделать то же самое с очень большими строками, чтобы буферу нужно было увеличивать или разбивать строки на части и ждать. Я не знаю, насколько большими могут быть буферы, но ваши текущие тестовые строки довольно короткие. quetzalcoatl
@BeniBela На самом делеqDebug() это функция, которая создает новыйQDebug пример. Таким образом, потоки не будут иметь доступате же данные, если не задействованы глобальные переменные. sashoalm
Этот ответ с 2006 года, хотя. Учитывая, что мой тестовый код выполняется без смешивания выходных данных, все могло измениться. Мне придется посмотреть на исходный код, вероятно. sashoalm
Это оQDebug который вы получаетеqDebug() неqDebug(..), Так что это не безопасно, если вы используетеqDebug() << "aaaaaaaaaaaaaaa", Ничего не говорит нам о версии в стиле C BeniBela
16

Если в документации qDebug () не указано, является ли она поточно-безопасной или нет, мы должны предположить, что это не так. Ответ, скорее всего, зависит от платформы: как qDebug () реализован на системном уровне (Linux, Windows, ...).

Вместо более широкого вопроса о безопасности потоков, я думаю, вы задавали более конкретный вопрос, такой как этот: «Приведет ли использование qDebug () в многопоточном приложении к чередующимся строкам вывода?» Ответ: «Да, иногда». как показали результаты, полученные @dmcontador выше. И вероятность увеличивается, когда строки для печати становятся длиннее, как объяснено @quetzalcoatl выше.

Ответ не зависит от того, используете ли вы qDebug ("...") или qDebug () << "...", поскольку оба они в конечном итоге вызовут код реализации системного уровня.

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

#include <QCoreApplication>
#include <QtConcurrent>

#define MAX_ITERS 10
#define MAX_LEN   10000

void print_a()
{
    QString a(MAX_LEN, 'a');

    for(int i = 0; i < MAX_ITERS; ++i) {
        qDebug().noquote() << a;
    }
}

void print_b()
{
    QString b(MAX_LEN, 'b');

    for(int i = 0; i < MAX_ITERS; ++i) {
        qDebug().noquote() << b;
    }
}

int main(int argc, char * argv[])
{
    QCoreApplication a(argc, argv);
    QtConcurrent::run(print_a);
    QtConcurrent::run(print_b);
    return 0;
}

Вероятность увеличивается при увеличении MAX_LEN.

Последующим вопросом будет: «Как использовать qDebug () для создания строк без чередования?» Одним из решений будет использование QMutex в каждой строке qDebug (). Обратите внимание, что я не пробовал это решение, которое не практично.
qDebug и сама QDebugявляется потокобезопасный. То, что вы видите как «чередующийся вывод», проистекает из того факта, что stderr (который Qt регистрирует по умолчанию в большинстве конфигураций) не буферизуется. Если у вас такие длинные строки, я предлагаю использовать другой приемник журналирования (например, syslogd в Linux). Вы можете легко проверить это, заменив qDebug () на вызовы fprintf (stderr, ...). kkoehne

Похожие вопросы