在Linux上的后台Qt线程中的非阻塞读取导致了EAGAIN

时间:2017-12-03 19:03:00

标签: c++ linux multithreading qt

以下是Qt中背景数据读取的最小示例(可在GitLab中找到)。程序打开文件并逐字节读取数据。流程如下:

//                            Flow
//
//          Widget                              Worker
//            +
//            | create thread
//            | create worker                     +
//            | move worker to thread             |
//            | start thread                      |
//            |                                   |
//            | start                     onStart |
//            |---------------------------------->|
//            |                                   |
//            | onReady                     ready |
//            |<----------------------------------|  .--<--.
//            |                 semaphore acquire |  |     |
//            | print data                        |  |     ^
//            |                                   |  v     |
//            | semaphore release                 |  |     |
//            |---------------------------------->|  `-->--
//            |                                   |
//            |                                   |
//            |                          finished |
//            |                                   |
//            | delete worker                     -
//            | detete thread
//            | quit application
//            -

以下代码有时(约1:30)在从常规文件中读取数据时导致EAGAIN错误代码。

$ ./rdqt ../main.cpp
Success 32768
$ ./rdqt ../main.cpp
Resource temporarily unavailable 32768

常规文件怎么可能?或者这是不正确的多线程实现的结果?

.
├── main.cpp
├── Widget.cpp
├── Widget.h
├── Worker.cpp
└── Worker.h

的main.cpp

#include <QApplication>
#include "Widget.h"

int main (int argc, char * argv [])
{
    QApplication application (argc, argv);

    if (argc > 1) {
        Widget widget (argv [1]);
        widget.show ();

        return application.exec ();
    }

    return EXIT_FAILURE;
}

Widget.h

#ifndef READ_DATA_WIDGET_H
#define READ_DATA_WIDGET_H

#include <QWidget>
#include <QThread>
#include <QSemaphore>
#include "Worker.h"                            

class Widget : public QWidget
{
    Q_OBJECT

    public:
        explicit Widget                 (const char *, QWidget * parent = nullptr);
        virtual ~Widget                 ();

    signals:
        void start                      ();

    public slots:
        void onReady                    (char);

    private:
        QThread                       * thread;
        QSemaphore                    * semaphore;
        Worker                        * worker;
};

#endif//READ_DATA_WIDGET_H

Widget.cpp

#include "Widget.h"
#include <QDebug>
#include <QApplication>

Widget::Widget (const char * path, QWidget * parent)
        : QWidget       (parent)
        , thread        {new QThread}
        , semaphore     {new QSemaphore (1)}
        , worker        {new Worker (path, semaphore)}
{
    connect (this, & Widget::start, worker, & Worker::onStart);
    connect (worker, & Worker::ready, this, & Widget::onReady);
    connect (worker, & Worker::finish, [this]() {
        thread->quit ();
        /*QApplication::quit ();*/
    });

    worker->moveToThread (thread);
    thread->start ();

    emit start          ();
}

Widget::~Widget ()
{
    worker->deleteLater ();
    thread->deleteLater ();
}

void Widget::onReady (char /*c*/)
{
    /*qDebug ("%c", c);*/
    semaphore->release ();
}

Worker.h

#ifndef READ_DATA_WORKER_H
#define READ_DATA_WORKER_H

#include <QObject>
#include <QSemaphore>

class Worker : public QObject
{
    Q_OBJECT

    public:
        explicit Worker                 (const char *, QSemaphore *);
        virtual ~Worker                 () = default;

    signals:
        void ready                      (char);
        void finish                     ();

    public slots:
        void onStart                    ();

    private:
        const char *                    path;
        QSemaphore                    * semaphore;
};

#endif//READ_DATA_WORKER_H

Worker.cpp

#include "Worker.h"
#include <QDebug>
#include <unistd.h>
#include <fcntl.h>

Worker::Worker (const char * path, QSemaphore * semaphore)
        : QObject   ()
        , path      {path}
        , semaphore {semaphore}
{
}

void Worker::onStart ()
{
    int file = open (path, O_RDONLY);
    char b;

    while (read (file, & b, 1) > 0) {
        semaphore->acquire ();
        emit ready (b);
    }
    qDebug () << strerror (errno) << (fcntl (file, F_GETFL) /*& O_NONBLOCK*/);

    emit finish ();
}

1 个答案:

答案 0 :(得分:2)

主要答案

好的,我终于明白了。当调用semaphore->acquire ();或发出emit ready (b);的信号时Qt锁定互斥锁时内部设置errno(Qt显然使用同步对象进行排队连接)。这里是如何调试errno发生变化的地方。我在Worker::onStart的开头添加了以下行:

qDebug() << QString("0x%1").arg(reinterpret_cast<quint64>(&errno), 0, 16);

并在调试器的下一行设置断点。有了这个地址(例如0x7fffda6ce668),我在gdb控制台中用watch *0x7fffda6ce668在gdb中添加了一个内存断点(如果你使用Qt Creator,启用Window - &gt; Views - &gt; Debugger Log)。我立刻得到了错误改变的回溯:

#0  0x00007ffff63964ae in syscall () at ../sysdeps/unix/sysv/linux/x86_64/syscall.S:42
#1  0x00007ffff6eb0610 in QBasicMutex::lockInternal() () from /home/(my-user-name)/apps/Qt5.5.0/5.5/gcc_64/lib/libQt5Core.so.5
#2  0x00007ffff70a7199 in QCoreApplication::postEvent(QObject*, QEvent*, int) () from /home/(my-user-name)/apps/Qt5.5.0/5.5/gcc_64/lib/libQt5Core.so.5
#3  0x00007ffff70d3286 in QMetaObject::activate(QObject*, int, int, void**) () from /home/(my-user-name)/apps/Qt5.5.0/5.5/gcc_64/lib/libQt5Core.so.5
#4  0x000000000040494f in Worker::ready (this=0x8d1550, _t1=0 '\\000') at moc_Worker.cpp:142
#5  0x0000000000403dee in Worker::onStart (this=0x8d1550) at ../qt/Worker.cpp:63

现在,QMutex在corelib / thread / qmutex_linux.cpp中实现并使用futex,有时会导致errno == 11。我不知道为什么会发生这种情况,抱歉,可能是某人的错误;)您可以查看qmutex_linux.cpp代码并尝试在网上找到相关信息。如果您对特定API调用产生错误感兴趣,可以在此调用之前设置errno=0并在调用后进行检查。顺便说一下,我测试了它没有任何文件io只是发送一个带有ready(0)的虚拟字符,结果是一样的。所以问题不在于文件io。

以前的答案(代码更改后大多无关紧要)

我认为您单独使用QMutex尝试实现的目标通常是使用QMutex和QWaitCondition:

void Worker::onStart ()
{
    // ...
    while (true) {
        QMutexLocker locker(&mutex);
        if(read (file, & b, 1) <= 0)
            break;
        emit ready (b);
        // waitCondition unlocks the mutex and 
        // waits till waitCondition wakeAll/wakeOne is called
        // signalling that Widget has finished processing
        waitCondition.wait(&mutex);
    }
    // ...
}

void Worker::onRequest ()
{
    // re-locks the mutex and continues the while cycle
    waitCondition.wakeAll();
}

此处waitConditionmutex之类的成员变量。我还没有检查过这段代码。它只是为了说明这个想法,你可能需要稍微改变一下。参考链接:QWaitCondition descriptionusage example