以下是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 ();
}
答案 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();
}
此处waitCondition
是mutex
之类的成员变量。我还没有检查过这段代码。它只是为了说明这个想法,你可能需要稍微改变一下。参考链接:QWaitCondition description和usage example。