[英]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.