Qt C++ 在 GUI 线程(Boost 线程)外显示图像
Qt C++ Displaying images outside the GUI thread (Boost thread)
我正在使用 VS2015 开发一个通过 Qt 实现其接口的 C++ 库。在库方面,3 个 boost 线程 不断从 3 个文件夹加载图像。我试图在 3 个不同的 QLabel(或等效的 QWidgets)中显示这些图像,因此线程主体包含此功能,
特别是通过利用 setPixmap 方法。尽管对该函数的调用受到升压互斥锁的保护,但我得到的异常可能是由于线程同步造成的。在寻找解决方案时,我已经意识到 QPixmap 小部件不是“线程安全”(不可重入)。我也尝试使用 QGraphicsView 但它又依赖于 QPixmap,因此我遇到了同样的问题。
所以我的问题是:是否存在 QPixmap 的替代方案以线程安全的方式在 Qt 中显示图像
方式?
我建议不要在 GUI 编程中使用多线程。尽管 Qt 通常提供多线程支持,但恕我直言,这些小部件并没有为此做好充分准备。
因此,要实现在不同线程中同时 运行 的图像加载器,我建议采用以下概念:
每个线程图像加载器提供一个私有缓冲区。 GUI 会不时检查(使用 QTimer
)这些缓冲区并更新其 QPixmap
。因为应该可以从 resp 访问缓冲区。当然,图像加载器线程以及 GUI 线程必须受到互斥保护。
我的示例代码testLoadImageMT.cc
:
#include <atomic>
#include <chrono>
#include <mutex>
#include <thread>
#include <QtWidgets>
// manually added types (normally provided by glib)
typedef unsigned guint;
typedef unsigned char guint8;
// the fluffy-cat image sample
struct Image {
guint width;
guint height;
guint bytes_per_pixel; /* 3:RGB, 4:RGBA */
guint8 pixel_data[1];
};
extern "C" const Image fluffyCat;
class ImageLoader {
private:
const Image &_img;
std::atomic<bool> _exit;
std::mutex _lock;
QImage _qImg;
std::thread _thread;
public: // main thread API
ImageLoader(const Image &img = fluffyCat):
_img(img),
_qImg(img.width, img.height, QImage::Format_RGB888),
_exit(false), _thread(&ImageLoader::loadImage, std::ref(*this))
{ }
~ImageLoader()
{
_exit = true;
_thread.join();
}
ImageLoader(const ImageLoader&) = delete;
void applyImage(QLabel &qLblImg)
{
std::lock_guard<std::mutex> lock(_lock);
qLblImg.setPixmap(QPixmap::fromImage(_qImg));
}
private: // thread private
void loadImage()
{
for (;;) {
{ std::lock_guard<std::mutex> lock(_lock);
_qImg.fill(0);
}
size_t i = 0;
for (int y = 0; y < (int)_img.height; ++y) {
for (int x = 0; x < (int)_img.width; ++x) {
const quint32 value
= _img.pixel_data[i + 2]
| (_img.pixel_data[i + 1] << 8)
| (_img.pixel_data[i + 0] << 16)
| (0xff << 24);
i += _img.bytes_per_pixel;
{ std::lock_guard<std::mutex> lock(_lock);
_qImg.setPixel(x, y, value);
}
if (_exit) return; // important: make thread co-operative
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // slow down CPU cooler
}
}
}
};
int main(int argc, char **argv)
{
// settings:
enum { N = 3 }; // number of images loaded/displayed
enum { Interval = 50 }; // update rate for GUI 50 ms -> 20 Hz (round about)
// build appl.
qDebug() << "Qt Version: " << QT_VERSION_STR;
QApplication app(argc, argv);
// build GUI
QWidget qMainWin;
QVBoxLayout qVBox;
QLabel *pQLblImgs[N];
for (int i = 0; i < N; ++i) {
qVBox.addWidget(
new QLabel(QString::fromUtf8("Image %1").arg(i + 1)));
qVBox.addWidget(
pQLblImgs[i] = new QLabel());
}
qMainWin.setLayout(&qVBox);
qMainWin.show();
// build image loaders
ImageLoader imgLoader[N];
// install timer
QTimer qTimer;
qTimer.setInterval(Interval); // ms
QObject::connect(&qTimer, &QTimer::timeout,
[&imgLoader, &pQLblImgs]() {
for (int i = 0; i < N; ++i) {
imgLoader[i].applyImage(*pQLblImgs[i]);
}
});
qTimer.start();
// exec. application
return app.exec();
}
抱歉,我使用 std::thread
而不是 boost::thread
,因为我没有使用后者的经验,也没有有效的安装。我相信(希望)差异很小。 QThread
本来是 "Qt native" 的替代方案,但又一次——没有经验。
为了简单起见,我只是从链接的二进制图像中复制数据(而不是从文件或其他任何地方加载一个)。因此,必须编译和链接第二个文件以使其成为 MCVE – fluffyCat.cc
:
/* GIMP RGB C-Source image dump (fluffyCat.cc) */
// manually added types (normally provided by glib)
typedef unsigned guint;
typedef unsigned char guint8;
extern "C" const struct {
guint width;
guint height;
guint bytes_per_pixel; /* 3:RGB, 4:RGBA */
guint8 pixel_data[16 * 16 * 3 + 1];
} fluffyCat = {
16, 16, 3,
"x1s520gw`fx`at[cx^cw^fu\itZerWn|ap~cv4jnzedq^fr^kzfhv^Ra"
"GRbMWdR\jXer^qw_16613554257045"
"1u~i357l{fly`jx\^nRlz_z6nlx`t~i11s2635"
"65620621562judgwcl~f26u}6h2"
"4q17z26{65v5006446076"
"11601652304{3p20xp~e{4^2"
"0n27g02{46z42r01703060"
"6634576042234\gRfrX40z2"
"5g52j45720{56u4254516"
"56671610464612725n{fj"
"xckyfu~fUX@VZCfnT11744172466466"
"2622501104032jvdbq\XkVJTDNTCCG8O"
"TE213775663676665642"
"602416044R_PL^HXkT<@2OP@`dP70744"
"077465177664077660"
"6234665jtbXdQTdNQYGU\KchV7527627"
"676307676676524113"
"31644\hTP^HL\FR[LMXI^dW52265674"
"064164166141440600"
"00||Y`SLVE>K9BJ<CN?VYP70266563067"
"7720424630504274shc^`TV`"
"RVbT>B4IS?PTD426450641616622"
"44176u527642\POMNBT]LNZH:<*<A*TV>OI;2"
"270435522626140124"
"22637c\WFH;MR>\`F~xP04[pqE12\g]=04`3"
"674302643713405244"
"2p66\63U24b44b67m02`47h~2"
"W63]40W77i|7RvzNlsGrtJwtLz}N{4RlxF",
};
我在 VS2013 中编译和测试,在 Windows 10(64 位)上使用 Qt 5.9.2。这是它的样子:
我使用 signal/slot 解决了:"non-GUI" 线程发出信号而不是显示图像,被调用的插槽在 GUI 线程内绘制 QLabel!
我正在使用 VS2015 开发一个通过 Qt 实现其接口的 C++ 库。在库方面,3 个 boost 线程 不断从 3 个文件夹加载图像。我试图在 3 个不同的 QLabel(或等效的 QWidgets)中显示这些图像,因此线程主体包含此功能, 特别是通过利用 setPixmap 方法。尽管对该函数的调用受到升压互斥锁的保护,但我得到的异常可能是由于线程同步造成的。在寻找解决方案时,我已经意识到 QPixmap 小部件不是“线程安全”(不可重入)。我也尝试使用 QGraphicsView 但它又依赖于 QPixmap,因此我遇到了同样的问题。 所以我的问题是:是否存在 QPixmap 的替代方案以线程安全的方式在 Qt 中显示图像 方式?
我建议不要在 GUI 编程中使用多线程。尽管 Qt 通常提供多线程支持,但恕我直言,这些小部件并没有为此做好充分准备。
因此,要实现在不同线程中同时 运行 的图像加载器,我建议采用以下概念:
每个线程图像加载器提供一个私有缓冲区。 GUI 会不时检查(使用 QTimer
)这些缓冲区并更新其 QPixmap
。因为应该可以从 resp 访问缓冲区。当然,图像加载器线程以及 GUI 线程必须受到互斥保护。
我的示例代码testLoadImageMT.cc
:
#include <atomic>
#include <chrono>
#include <mutex>
#include <thread>
#include <QtWidgets>
// manually added types (normally provided by glib)
typedef unsigned guint;
typedef unsigned char guint8;
// the fluffy-cat image sample
struct Image {
guint width;
guint height;
guint bytes_per_pixel; /* 3:RGB, 4:RGBA */
guint8 pixel_data[1];
};
extern "C" const Image fluffyCat;
class ImageLoader {
private:
const Image &_img;
std::atomic<bool> _exit;
std::mutex _lock;
QImage _qImg;
std::thread _thread;
public: // main thread API
ImageLoader(const Image &img = fluffyCat):
_img(img),
_qImg(img.width, img.height, QImage::Format_RGB888),
_exit(false), _thread(&ImageLoader::loadImage, std::ref(*this))
{ }
~ImageLoader()
{
_exit = true;
_thread.join();
}
ImageLoader(const ImageLoader&) = delete;
void applyImage(QLabel &qLblImg)
{
std::lock_guard<std::mutex> lock(_lock);
qLblImg.setPixmap(QPixmap::fromImage(_qImg));
}
private: // thread private
void loadImage()
{
for (;;) {
{ std::lock_guard<std::mutex> lock(_lock);
_qImg.fill(0);
}
size_t i = 0;
for (int y = 0; y < (int)_img.height; ++y) {
for (int x = 0; x < (int)_img.width; ++x) {
const quint32 value
= _img.pixel_data[i + 2]
| (_img.pixel_data[i + 1] << 8)
| (_img.pixel_data[i + 0] << 16)
| (0xff << 24);
i += _img.bytes_per_pixel;
{ std::lock_guard<std::mutex> lock(_lock);
_qImg.setPixel(x, y, value);
}
if (_exit) return; // important: make thread co-operative
}
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // slow down CPU cooler
}
}
}
};
int main(int argc, char **argv)
{
// settings:
enum { N = 3 }; // number of images loaded/displayed
enum { Interval = 50 }; // update rate for GUI 50 ms -> 20 Hz (round about)
// build appl.
qDebug() << "Qt Version: " << QT_VERSION_STR;
QApplication app(argc, argv);
// build GUI
QWidget qMainWin;
QVBoxLayout qVBox;
QLabel *pQLblImgs[N];
for (int i = 0; i < N; ++i) {
qVBox.addWidget(
new QLabel(QString::fromUtf8("Image %1").arg(i + 1)));
qVBox.addWidget(
pQLblImgs[i] = new QLabel());
}
qMainWin.setLayout(&qVBox);
qMainWin.show();
// build image loaders
ImageLoader imgLoader[N];
// install timer
QTimer qTimer;
qTimer.setInterval(Interval); // ms
QObject::connect(&qTimer, &QTimer::timeout,
[&imgLoader, &pQLblImgs]() {
for (int i = 0; i < N; ++i) {
imgLoader[i].applyImage(*pQLblImgs[i]);
}
});
qTimer.start();
// exec. application
return app.exec();
}
抱歉,我使用 std::thread
而不是 boost::thread
,因为我没有使用后者的经验,也没有有效的安装。我相信(希望)差异很小。 QThread
本来是 "Qt native" 的替代方案,但又一次——没有经验。
为了简单起见,我只是从链接的二进制图像中复制数据(而不是从文件或其他任何地方加载一个)。因此,必须编译和链接第二个文件以使其成为 MCVE – fluffyCat.cc
:
/* GIMP RGB C-Source image dump (fluffyCat.cc) */
// manually added types (normally provided by glib)
typedef unsigned guint;
typedef unsigned char guint8;
extern "C" const struct {
guint width;
guint height;
guint bytes_per_pixel; /* 3:RGB, 4:RGBA */
guint8 pixel_data[16 * 16 * 3 + 1];
} fluffyCat = {
16, 16, 3,
"x1s520gw`fx`at[cx^cw^fu\itZerWn|ap~cv4jnzedq^fr^kzfhv^Ra"
"GRbMWdR\jXer^qw_16613554257045"
"1u~i357l{fly`jx\^nRlz_z6nlx`t~i11s2635"
"65620621562judgwcl~f26u}6h2"
"4q17z26{65v5006446076"
"11601652304{3p20xp~e{4^2"
"0n27g02{46z42r01703060"
"6634576042234\gRfrX40z2"
"5g52j45720{56u4254516"
"56671610464612725n{fj"
"xckyfu~fUX@VZCfnT11744172466466"
"2622501104032jvdbq\XkVJTDNTCCG8O"
"TE213775663676665642"
"602416044R_PL^HXkT<@2OP@`dP70744"
"077465177664077660"
"6234665jtbXdQTdNQYGU\KchV7527627"
"676307676676524113"
"31644\hTP^HL\FR[LMXI^dW52265674"
"064164166141440600"
"00||Y`SLVE>K9BJ<CN?VYP70266563067"
"7720424630504274shc^`TV`"
"RVbT>B4IS?PTD426450641616622"
"44176u527642\POMNBT]LNZH:<*<A*TV>OI;2"
"270435522626140124"
"22637c\WFH;MR>\`F~xP04[pqE12\g]=04`3"
"674302643713405244"
"2p66\63U24b44b67m02`47h~2"
"W63]40W77i|7RvzNlsGrtJwtLz}N{4RlxF",
};
我在 VS2013 中编译和测试,在 Windows 10(64 位)上使用 Qt 5.9.2。这是它的样子:
我使用 signal/slot 解决了:"non-GUI" 线程发出信号而不是显示图像,被调用的插槽在 GUI 线程内绘制 QLabel!