Qt继承QThread的多线程使用方法

2024-01-02 13:38

本文主要是介绍Qt继承QThread的多线程使用方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Qt使用多线程的一些心得——1.继承QThread的多线程使用方法

文章目录
1.摘要
2.Qt多线程方法1 继承QThread
2.1写一个继承于QThread的线程
2.2 QThread的几个函数quit、exit、terminate函数
2.3 正确的终止一个线程
2.4 如何正确启动一个线程
2.4.1正确的启动一个全局线程(和UI一直存在的线程)
2.4.2 如何启动一个局部线程(用完即释放的线程)
3. 继承QThread的一些总结

使用QObject实现多线的方法见:http://blog.csdn.net/czyt1988/article/details/71194457
1.摘要
Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。
Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承QObject的方法更加灵活。这里要记录的是如何正确的创建一个线程,特别是如何正确的退出一个线程。

本文先介绍QThread的普通用法,这个用法可能网上很多文章都介绍过,如果已经了解大可跳过此节,本文重点介绍线程退出的几种方法,根据需求正确的创建和退出线程等问题。

2.Qt多线程方法1 继承QThread
在使用继承QThread的run方法之前需要了解一条规则:

QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里

QThread只有run函数是在新线程里的
QThread只有run函数是在新线程里的
QThread只有run函数是在新线程里的

重要的事情说3遍!!!

,如果QThread是在ui所在的线程里生成,那么QThread的其他非run函数都是和ui线程一样的,所以,QThread的继承类的其他函数尽量别要有太耗时的操作,要确保所有耗时的操作都在run函数里。
在UI线程下调用QThread的非run函数(其实也不应该直接调用run函数,而应该使用start函数),和执行普通函数无区别,这时,如果这个函数要对QThread的某个变量进行变更,而这个变量在run函数里也会被用到,这时就需要注意加锁的问题,因为可能这个变量前几毫秒刚刚在run中调用,再调用时已经被另外的线程修改了。

2.1写一个继承于QThread的线程
本文的重点不是教会你继承run写一个多线程,任何有编程基础的5分钟就能学会使用QThread的方法,本文真正要讲的是后面那几节,如如何安全的退出一个线程,如何开启一个临时线程,运行结束马上关闭等问题。如果你对QThread有初步了解,那么可以略过这节,但你最好看看这节后面提出的几个问题。

任何继承于QThread的线程都是通过继承QThread的run函数来实现多线程的,因此,必须重写QThread的run函数,把复杂逻辑写在QThread的run函数中。

看看一个普通继承QThread的例子:
头文件:

#ifndef THREADFROMQTHREAD_H
#define THREADFROMQTHREAD_H
#include <QThread>class ThreadFromQThread : public QThread
{Q_OBJECT
signals:void message(const QString& info);void progress(int present);
public:ThreadFromQThread(QObject* par);~ThreadFromQThread();void setSomething();void getSomething();void setRunCount(int count);void run();void doSomething();
private:int m_runCount;
};#endif // THREADFROMQTHREAD_H

cpp文件:


```cpp
#include "ThreadFromQThread.h"
#include <QDebug>
ThreadFromQThread::ThreadFromQThread(QObject* par) : QThread(par)
,m_runCount(20)
{}ThreadFromQThread::~ThreadFromQThread()
{qDebug() << "ThreadFromQThread::~ThreadFromQThread()";
}void ThreadFromQThread::setSomething()
{msleep(500);QString str = QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId());emit message(str);
}void ThreadFromQThread::getSomething()
{msleep(500);emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
}void ThreadFromQThread::setRunCount(int count)
{m_runCount = count;emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
}void ThreadFromQThread::run()
{int count = 0;QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());emit message(str);while(1){sleep(1);++count;emit progress(((float)count / m_runCount) * 100);emit message(QString("ThreadFromQThread::run times:%1").arg(count));doSomething();if(m_runCount == count){break;}}
}void ThreadFromQThread::doSomething()
{msleep(500);emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));    
}

这个简单的例子有一个Qt类常见的内容,包含了普通方法,信号槽,和一个run函数。这里函数setSomething();进行了500ms的延迟,getSomething同理。这是为了验证在QThread::run()之外调用QThread成员函数不会运行在新线程里。

上面代码用到了QThread::currentThreadId()这是一个静态函数,用于返回当前线程句柄,这个值除了区分以外没有别的用处。

为了验证这个线程,编写一个简单的界面,这个界面主要用于验证如下几个问题:、

在UI线程调用setSomething();函数和getSomething();函数会不会卡顿?
在UI线程调用QThread::quit()或QThread::exit()函数会不会停止线程?
在UI线程调用QThread::terminate函数会不会停止线程?
如何正确的退出线程?
2.2 QThread的几个函数quit、exit、terminate函数
为了验证上面这些,编写一个简单的界面如下图所示:


```cpp
#include "Widget.h"
#include "ui_Widget.h"
#include "ThreadFromQThread.h"
#include <QDebug>
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);//控件初始化ui->progressBar->setRange(0,100);ui->progressBar->setValue(0);ui->progressBar_heart->setRange(0,100);ui->progressBar_heart->setValue(0);//按钮的信号槽关联connect(ui->pushButton_qthread1,&QPushButton::clicked,this,&Widget::onButtonQThreadClicked);connect(ui->pushButton_qthread1_setSomething,&QPushButton::clicked,this,&Widget::onButtonQthread1SetSomethingClicked);connect(ui->pushButton_qthread1_getSomething,&QPushButton::clicked,this,&Widget::onButtonQthread1GetSomethingClicked);connect(ui->pushButton_qthreadQuit,&QPushButton::clicked,this,&Widget::onButtonQthreadQuitClicked);connect(ui->pushButton_qthreadTerminate,&QPushButton::clicked,this,&Widget::onButtonQthreadTerminateClicked);connect(ui->pushButton_qthreadExit,&QPushButton::clicked,this,&Widget::onButtonQThreadExitClicked);connect(ui->pushButton_doSomthing,&QPushButton::clicked,this,&Widget::onButtonQThreadDoSomthingClicked);connect(ui->pushButton_qthreadRunLocal,&QPushButton::clicked,this,&Widget::onButtonQThreadRunLoaclClicked);//connect(ui->pushButton_qobjectStart,&QPushButton::clicked,this,&Widget::onButtonObjectMove2ThreadClicked);connect(ui->pushButton_objQuit,&QPushButton::clicked,this,&Widget::onButtonObjectQuitClicked);//connect(&m_heart,&QTimer::timeout,this,&Widget::heartTimeOut);m_heart.setInterval(100);//全局线程的创建m_thread = new ThreadFromQThread(this);connect(m_thread,&ThreadFromQThread::message,this,&Widget::receiveMessage);connect(m_thread,&ThreadFromQThread::progress,this,&Widget::progress);connect(m_thread,&QThread::finished,this,&Widget::onQThreadFinished);m_heart.start();
}Widget::~Widget()
{qDebug() << "start destroy widget";m_thread->stopImmediately();//由于此线程的父对象是Widget,因此退出时需要进行判断m_thread->wait();delete ui;qDebug() << "end destroy widget";
}void Widget::onButtonQThreadClicked()
{ui->progressBar->setValue(0);if(m_thread->isRunning()){return;}m_thread->start();
}void Widget::progress(int val)
{ui->progressBar->setValue(val);
}void Widget::receiveMessage(const QString &str)
{ui->textBrowser->append(str);
}void Widget::heartTimeOut()
{static int s_heartCount = 0;++s_heartCount;if(s_heartCount > 100){s_heartCount = 0;}ui->progressBar_heart->setValue(s_heartCount);
}void Widget::onButtonQthread1SetSomethingClicked()
{m_thread->setSomething();
}void Widget::onButtonQthread1GetSomethingClicked()
{m_thread->getSomething();
}void Widget::onButtonQthreadQuitClicked()
{ui->textBrowser->append("m_thread->quit() but not work");m_thread->quit();
}void Widget::onButtonQthreadTerminateClicked()
{m_thread->terminate();
}void Widget::onButtonQThreadDoSomthingClicked()
{m_thread->doSomething();
}void Widget::onButtonQThreadExitClicked()
{m_thread->exit();
}void Widget::onQThreadFinished()
{ui->textBrowser->append("ThreadFromQThread finish");
}

界面为上面提到的几个问题提供了按钮, 界面有一个心跳进度条,它是主程序的定时器控制,每100ms触发用于证明主程序的ui线程没有卡死。第二个进度条由线程控制。

点击"QThread run"按钮,触发onButtonQThreadClicked槽,子线程会运行,子线程运行起来后,会打印

…/QtThreadTest/ThreadFromQThread.cpp->run,thread id:2900388672

可以确定线程运行的id是2900388672
子线程是个循环,每次循环都会有打印信息:

ThreadFromQThread::run times:1
doSomething->…/QtThreadTest/ThreadFromQThread.cpp,thread id:2900388672
ThreadFromQThread::run times:2
doSomething->…/QtThreadTest/ThreadFromQThread.cpp,thread id:2900388672

doSomething是在run函数里调用,其线程id是2900388672,可见这时doSomething函数是运行在子线程里的。

这时,我在界面点击getSomething,setSomething,doSomething会打印:

getSomething->…/QtThreadTest/ThreadFromQThread.cpp,thread id:3021526784
setSomething->…/QtThreadTest/ThreadFromQThread.cpp,thread id:3021526784
doSomething->…/QtThreadTest/ThreadFromQThread.cpp,thread id:3021526784

说明在非run函数里调用QThread的成员函数,并不是在线程里运行(3021526784是widget所在线程)

这时我点击quit,thread并没进行任何处理,QThread在不调用exec()情况下是exit函数和quit函数是没有作用的。

m_thread->quit() but not work

点击terminate按钮,线程马上终止,打印:

ThreadFromQThread finish

动态图如下图所示:

因此可以看出quit和exit函数都不会中途终端线程,要马上终止一个线程可以使用terminate函数,但这个函数存在非常不安定因素,不推荐使用。那么如何安全的终止一个线程呢?

2.3 正确的终止一个线程
最简单的方法是添加一个bool变量,通过主线程修改这个bool变量来进行终止,但这样有可能引起访问冲突,需要加锁
我们需要在原来的头文件加上如下语句:

#include <QMutex>class ThreadFromQThread : public QThread
{
...........
public slots:void stopImmediately();
private:QMutex m_lock;bool m_isCanRun;
.........
};

run函数需要进行修改:

void ThreadFromQThread::stopImmediately()
{QMutexLocker locker(&m_lock);m_isCanRun = false;
}void ThreadFromQThread::run()
{int count = 0;m_isCanRun = true;//标记可以运行QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((unsigned int)QThread::currentThreadId());emit message(str);while(1){sleep(1);++count;emit progress(((float)count / m_runCount) * 100);emit message(QString("ThreadFromQThread::run times:%1").arg(count));doSomething();if(m_runCount == count){break;}{QMutexLocker locker(&m_lock);if(!m_isCanRun)//在每次循环判断是否可以运行,如果不行就退出循环{return;}}}
}

QMutexLocker可以安全的使用QMutex,以免忘记解锁(有点类似std::unique_ptr),这样每次循环都会看看是否要马上终止。
在线程需要马上退出时,可以在外部调用stopImmediately()函数终止线程,之前的例子可以知道,由于在主线程调用QThread非run()函数的函数都是在主线程运行,因此,在主线程调用类似m_thread->stopImmediately()会几乎马上把线程的成员变量m_isCanRun设置为false(面对多线程问题要用面向过程的思维思考),因此在子线程的run函数的循环中遇到m_isCanRun的判断后就会退出run函数,继承QThread的函数在运行完run函数后就视为线程完成,会发射finish信号。

2.4 如何正确启动一个线程
线程的启动有几种方法,这几种方法设计到它的父对象归属问题,和如何删除他的问题。首先要搞清楚这个线程是否和UI的生命周期一致,直到UI结束线程才结束,还是这个线程只是临时生成,等计算完成就销毁。

第一种情况的线程在创建时会把生成线程的窗体作为它的父对象,这样窗体结束时会自动析构线程的对象。但这时候要注意一个问题,就是窗体结束时线程还未结束如何处理,如果没有处理这种问题,你会发现关闭窗口时会导致程序崩溃。往往这种线程是一个监控线程,如监控某个端口的线程。为了好区分,暂时叫这种叫全局线程,它在UI的生命周期中都存在。

第二种情况是一种临时线程,这种线程一般是突然要处理一个大计算,为了不让UI假死需要触发的线程,这时需要注意一个问题,就是在线程还没计算完成,用户突然终止或变更时如何处理,这种线程往往更多见且更容易出错,如打开一个大文件,显示一个大图片,用户可能看一个大图片还没等图片处理完成又切换到下一个图片,这时绘图线程要如何处理才能顺利解决?为了好区分,暂时叫这种叫局部线程,它在UI的生命周期中仅仅是某时刻才会触发,然后销毁。

这就涉及到如何终止正在执行的线程这个问题!

2.4.1正确的启动一个全局线程(和UI一直存在的线程)
我发现大部分网上的教程都是教你创建一个全局的线程,但往往这种线程用的不多,也比较好管理,需要注意的是程序退出时对线程的处理问题。
在ui的头文件中声明一个线程的指针

widget.h:

ThreadFromQThread* m_thread;
1
wodget.cpp:class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = 0);~Widget();
private slots:void onButtonQThreadClicked();void onButtonQthread1SetSomethingClicked();void onButtonQthread1GetSomethingClicked();void onButtonQthreadQuitClicked();void onButtonQthreadTerminateClicked();void onButtonQThreadDoSomthingClicked();void onQThreadFinished();
......void progress(int val);void receiveMessage(const QString& str);void heartTimeOut();
private:Ui::Widget *ui;ThreadFromQThread* m_thread;QTimer m_heart;
......

先看窗体生成的构造函数

Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget),m_objThread(NULL)
{ui->setupUi(this);//控件初始化ui->progressBar->setRange(0,100);ui->progressBar->setValue(0);ui->progressBar_heart->setRange(0,100);ui->progressBar_heart->setValue(0);//按钮的信号槽关联connect(ui->pushButton_qthread1,&QPushButton::clicked,this,&Widget::onButtonQThreadClicked);connect(ui->pushButton_qthread1_setSomething,&QPushButton::clicked,this,&Widget::onButtonQthread1SetSomethingClicked);connect(ui->pushButton_qthread1_getSomething,&QPushButton::clicked,this,&Widget::onButtonQthread1GetSomethingClicked);connect(ui->pushButton_qthreadQuit,&QPushButton::clicked,this,&Widget::onButtonQthreadQuitClicked);connect(ui->pushButton_qthreadTerminate,&QPushButton::clicked,this,&Widget::onButtonQthreadTerminateClicked);connect(ui->pushButton_doSomthing,&QPushButton::clicked,this,&Widget::onButtonQThreadDoSomthingClicked);//心跳的关联connect(&m_heart,&QTimer::timeout,this,&Widget::heartTimeOut);m_heart.setInterval(100);//全局线程的创建//全局线程创建时可以把窗体指针作为父对象m_thread = new ThreadFromQThread(this);//关联线程的信号和槽connect(m_thread,&ThreadFromQThread::message,this,&Widget::receiveMessage);//connect(m_thread,&ThreadFromQThread::progress,this,&Widget::progress);connect(m_thread,&QThread::finished,this,&Widget::onQThreadFinished);//UI心跳开始m_heart.start();
}

由于是全局存在的线程,因此在窗体创建时就创建线程,可以把线程的父对象设置为窗体,这时需要注意,别手动delete线程指针。用于你的QThread是在Qt的事件循环里面,手动delete会发生不可预料的意外。理论上所有QObject都不应该手动delete,如果没有多线程,手动delete可能不会发生问题,但是多线程情况下delete非常容易出问题,那是因为有可能你要删除的这个对象在Qt的事件循环里还排队,但你却已经在外面删除了它,这样程序会发生崩溃。

如果你确实要删除,请参阅void QObject::deleteLater () [slot]这个槽,这个槽非常有用,尤其是对局部线程来说。后面会经常用到它用于安全的结束线程。

在需要启动线程的地方调用start函数即可启动线程。

void Widget::onButtonQThreadClicked()
{ui->progressBar->setValue(0);if(m_thread->isRunning()){return;}m_thread->start();
}

如果线程已经运行,你重复调用start其实是不会进行任何处理。

一个全局线程就那么简单,要用的时候start一下就行。真正要注意的是如何在ui结束时把线程安全退出。

在widget的析构函数应该这样写:

Widget::~Widget()
{qDebug() << "start destroy widget";m_thread->stopImmediately();m_thread->wait();delete ui;qDebug() << "end destroy widget";
}

这里要注意的是m_thread->wait();这一句,这一句是主线程等待子线程结束才能继续往下执行,这样能确保过程是单一往下进行的,也就是不会说子线程还没结束完,主线程就destrioy掉了(m_thread的父类是主线程窗口,主线程窗口如果没等子线程结束就destroy的话,会顺手把m_thread也delete这时就会奔溃了),因此wait的作用就是挂起,一直等到子线程结束。

还有一种方法是让QThread自己删除自己,就是在new线程时,不指定父对象,通过绑定**void QObject::deleteLater () [slot]**槽让它自动释放。这样在widget析构时可以免去m_thread->wait();这句。

2.4.2 如何启动一个局部线程(用完即释放的线程)
启动一个局部线程(就是运行完自动删除的线程)方法和启动全局线程差不多,但要关联多一个槽函数,就是之前提到的void QObject::deleteLater () [slot],这个槽函数是能安全释放线程资源的关键(直接delete thread指针不安全)。

简单的例子如下:

void Widget::onButtonQThreadRunLoaclClicked()
{//局部线程的创建的创建ThreadFromQThread* thread = new ThreadFromQThread(NULL);//这里父对象指定为NULLconnect(thread,&ThreadFromQThread::message,this,&Widget::receiveMessage);connect(thread,&ThreadFromQThread::progress,this,&Widget::progress);connect(thread,&QThread::finished,this,&Widget::onQThreadFinished);connect(thread,&QThread::finished,thread,&QObject::deleteLater);//线程结束后调用deleteLater来销毁分配的内存thread->start();
}

这个例子还是启动之前的线程,但不同的是:

new ThreadFromQThread(NULL);并没有给他指定父对象
connect(thread,&QThread::finished ,thread,&QObject::deleteLater);线程结束后调用deleteLater来销毁分配的内存。
再线程运行完成,发射finished信号后会调用deleteLater函数,在确认消息循环中没有这个线程的对象后会销毁。
但是要注意避免重复点按钮重复调用线程的情况,对于一些需求,线程开启后再点击按钮不会再重新生成线程,一直等到当前线程执行完才能再次点击按钮,这种情况很好处理,加个标记就可以实现,也一般比较少用。

另外更多见的需求是,再次点击按钮,需要终结上次未执行完的线程,重新执行一个新线程。这种情况非常多见,例如一个普通的图片浏览器,都会有下一张图和上一张图这种按钮,浏览器加载图片一般都在线程里执行(否则点击超大图片时图片浏览器会类似卡死的状态),用户点击下一张图片时需要终止正在加载的当前图片,加载下一张图片。你不能要求客户要当前图片加载完才能加载下一张图片,这就几乎沦为单线程了。这时候,就需要终止当前线程,开辟新线程加载下一个图片。

这时,上面的函数将会是大概这个样子的

UI的头文件需要一个成员变量记录正在运行的线程

private slots:void onLocalThreadDestroy(QObject* obj);
private:QThread* m_currentRunLoaclThread;

运行生成临时线程的函数将变为

void Widget::onButtonQThreadRunLoaclClicked()
{//局部线程的创建的创建if(m_currentRunLoaclThread){m_currentRunLoaclThread->stopImmediately();}ThreadFromQThread* thread = new ThreadFromQThread(NULL);connect(thread,&ThreadFromQThread::message,this,&Widget::receiveMessage);connect(thread,&ThreadFromQThread::progress,this,&Widget::progress);connect(thread,&QThread::finished,this,&Widget::onQThreadFinished);connect(thread,&QThread::finished,thread,&QObject::deleteLater);//线程结束后调用deleteLater来销毁分配的内存connect(thread,&QObject::destroyed,this,&Widget::onLocalThreadDestroy);m_currentRunLoaclThread = thread;thread->start();
}void Widget::onLocalThreadDestroy(QObject *obj)
{if(m_currentRunLoaclThread == obj){m_currentRunLoaclThread = NULL;}
}

这里用一个临时变量记录当前正在运行的局部线程,由于线程结束时会销毁自己,因此要通知主线程把这个保存线程指针的临时变量设置为NULL
因此用到了QObject::destroyed信号,在线程对象析构时通知UI把m_currentRunLoaclThread设置为nullptr;

  1. 继承QThread的一些总结
    QThread只有run函数是在新线程里的

在QThread执行start函数之后,run函数还未运行完毕,再次start会出现什么后果?

答案是:不会发生任何结果,QThread还是继续执行它的run函数,run函数不会被重新调用。虽然在线程未结束时调用start不会出现什么结果,但为了谨慎起见,还是建议在start之前进行判断:

void Widget::onButtonQThreadClicked()
{ui->progressBar->setValue(0);if(m_thread->isRunning()){return;}m_thread->start();
}

这种调用方法估计了解过QThread的都知道

在线程运行过程调用quit函数有什么效果
答案是:不会发生任何效果,QThread不会因为你调用quit函数而退出正在运行到一半的run,正确退出线程的方法上面有介绍。那quit到底有什么用的呢,这要到下篇才能看出它的作用。使用moveToThread方法执行多线程时,这个函数将有大作用。

程序在退出时要判断各线程是否已经退出,没退出的应该让它终止
如果不进行判断,很可能程序退出时会崩溃。如果线程的父对象是窗口对象,那么在窗体的析构函数中,还需要调用wait函数等待线程完全结束再进行下面的析构。

善用QObject::deleteLater 和 QObject::destroyed来进行内存管理
由于多线程环境你不可预料下一步是哪个语句执行,因此,加锁和自动删除是很有用的工具,加锁是通过效率换取安全,用Qt的信号槽系统可以更有效的处理这些问题。
————————————————
版权声明:本文为CSDN博主「尘中远」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/czyt1988/article/details/64441443

这篇关于Qt继承QThread的多线程使用方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/562609

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施: