簡體   English   中英

Linux上后台Qt線程中的非阻塞讀取導致EAGAIN

[英]Non-blocking reading in background Qt thread on Linux caused EAGAIN

這是在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 ();
}

工人

#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

工作人員

#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 ();
}

主要答案

好的,我終於明白了。 調用semaphore->acquire ();時,通過Qt鎖定互斥鎖時會在內部設置errno semaphore->acquire (); 或當emit ready (b);的信號emit ready (b); (顯然,Qt使用同步對象進行排隊連接)。 這是調試errno更改發生位置的方法。 我在Worker::onStart的開頭添加了以下行:

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

並在調試器的下一行設置斷點。 具有該地址(例如0x7fffda6ce668)后,我在gdb控制台中使用watch *0x7fffda6ce668在gdb中添加了一個內存斷點(如果使用Qt Creator,請啟用watch *0x7fffda6ce668 > Debugger Log)。 我立即得到了errno更改的回溯信息:

#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 ,並在調用之后進行檢查。 順便說一句,我在沒有任何文件的情況下測試了它,只是發送了一個帶有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();
}

在這里, waitCondition是一個成員變量,例如mutex 我尚未檢查此代碼。 只是為了說明這個想法,您可能需要對其稍作更改。 參考鏈接:QWaitCondition 描述用法示例

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM