Qt学习笔记:多线程的使用

2023-05-16

文章目录

  • 前言
  • 1. 何时使用线程
  • 2. QThread类实现多线程
    • 2.1 多线程的实现方法
    • 2.2 线程休眠
    • 2.3 正确结束线程
  • 3. 线程同步
    • 3.1 互斥量
    • 3.2 信号量
    • 3.3 条件变量
  • 4. 线程池
  • 参考资料


前言

程序中调用耗时的操作(如大批量I/O、实时通信、大计算量算法等)会造成用户界面的卡顿,通常将这一类操作放在子线程中来解决这一问题。


1. 何时使用线程

在进行桌面应用程序开发的时候,如果应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作,这种情况下就需要使用多线程。在 Qt 中使用多线程应遵循以下原则:

  • 遵循前后台分离的设计原则,前台主线程负责窗口事件处理或者窗口控件数据的更新,子线程负责后台的业务逻辑处理,不能对窗口对象做任何操作。
  • 线程之间进行数据的传递,使用Qt中的信号槽机制。

2. QThread类实现多线程

2.1 多线程的实现方法

Qt 使用QThread类实现多线程,具体实现方法有两种:一种是创建继承于QThread的线程类,并重写run()方法(不推荐);另一种则是创建继承于QObject的工作类(Worker Class),在主线程中通过QObject::moveToThread()方法将工作类移动到子线程中(推荐)。

1) subclass QThread

注意:这种使用方法并不推荐,Bradley T. Hughes2010 专门写了篇博客 You’re doing it wrong…来讨论QThread的正确使用方法。

/*------------------------------WorkerThread-----------------------------------*/
class WorkerThread : public QThread
{
    Q_OBJECT
    void run() override {
        qDebug() << "Thread in run is" << QThread::currentThread();
        connect(ptcpSocket, &QTcpSocket::readyRead, this, [=]()
    	{
        	qDebug() << "Thread in slot is" << QThread::currentThread();
            QString result;
        	/* ... here is the expensive or blocking operation ... */
        	emit resultReady(result);
    	});
        exec();
    }
    
signals:
    void resultReady(const QString &s);
};

/*------------------------------MainWindow-----------------------------------*/
void MainWindow::startWorkInAThread()
{
    WorkerThread *workerThread = new WorkerThread(this);
    connect(workerThread, &WorkerThread::resultReady, this, &MainWindow::handleResults);
    connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
    workerThread->start();
}

通过qDebug() << QThread::currentThread();可以打印当前代码所运行的线程。

在本例中,原本是想在子线程中实现tcp通信的数据接收功能,但实际上打印槽函数运行的线程却是主线程,原因在于Qt的信号槽机制:

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

connect函数中,最后一个形参为连接类型Qt::ConnectionType,缺省类型为Qt::AutoConnection。信号发送时,确定连接类型:当信号发送者和接收者在同一线程内,使用Qt::DirectConnection;当信号发送者和接收者不在同一线程,使用Qt::QueuedConnection

Qt::DirectConnection:

信号发送时槽函数立刻被执行,此时槽函数处于发送信号的一线程。

Qt::QueuedConnection:

当控制权回到接收者所在的事件循环时,槽函数才被执行,此时槽函数在接收者所在线程。

因此,在例子中WorkerThread中的connect使用Qt::QueuedConnection作为连接类型。同时由于MainWindow中定义的workerThread对象处于主线程,因此槽函数也将在主线程中运行。如果你看过Qt自带的例子,你会发现 QThread 中 slot 和 run函数共同操作的对象,都会用QMutex锁住。为什么?因为 slot 和 run 处于不同线程,需要线程间的同步!

2) worker-object

Qt 4.4 版本之后完善了线程的亲和性以及信号槽机制,我们有了更为安全的使用线程的方式,即 QObject::moveToThread() 。使用信号和槽时根本不用考虑多线程的存在。也不用使用QMutex来进行同步,Qt的事件循环会自己自动处理。

/*---------------------------Worker----------------------------*/
class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(){
        pTimer = new QTimer;
        connect(pTimer,&QTimer::timeout,this,&Worker::onTimeout);
    }

public slots:
    void doWork(const QString &parameter) {
        QString result;
        /* ... here is the expensive or blocking operation ... */
        pTimer->start(interval);
        emit resultReady(result);
    }
    
private slots:
    void onTimout(){
        /* do something */
        qDebug() << "Thread in slot is:" << QThread::currrentThread();
    }

signals:
    void resultReady(const QString &result);
    
private:
    QTimer *pTimer;
};


/*---------------------------Controller----------------------------*/
class Controller : public QObject
{
    Q_OBJECT
    QThread workerThread;
public:
    Controller() {
        // 注意千万不要给创建的Worker对象指定父对象
        Worker *worker = new Worker;
        worker->moveToThread(&workerThread);
        connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
        connect(this, &Controller::operate, worker, &Worker::doWork);
        connect(worker, &Worker::resultReady, this, &Controller::handleResults);
        workerThread.start();
    }
    ~Controller() {
        workerThread.quit();
        workerThread.wait();
    }
public slots:
    void handleResults(const QString &);
signals:
    void operate(const QString &);
};

相较于subclass QThread,使用worker-object方法最突出的优势在于:

  • 可以在子线程内自由地使用信号槽。在示例代码中,Worker类内部定义了一个定时器,响应定时器到时的槽函数onTimout()也是运行在子线程中的。
  • 可以在子线程内部定义多个槽函数,以实现不同的业务逻辑,而subclass QThread只能将所有任务塞进run()函数处理。
  • 可以将多个工作对象移动到一个子线程中,但需要注意的是,移动到一个线程中的工作对象是线性处理的,多个任务不能同步进行。

使用worker-object需要注意的是:

  • QObject::moveToThread()的作用是将槽函数放在指定的线程中调用。仅有槽函数在指定线程中调用,包括构造函数都仍然在主线程中调用!!!
  • 初始化worker对象时不要给他指定父对象。如果给work指定了父对象,就无法使用moveToThread()(提示: QObject::moveToThread: Cannot move objects with a parent)。

2.2 线程休眠

接口功能
void sleep(unsigned long secs)休眠(s)
void msleep(unsigned long msecs)休眠(ms)
void usleep(unsigned long usecs)休眠(us)

2.3 正确结束线程

删除正在运行的QThread将导致程序奔溃,需要正确地结束线程。

退出线程:quit()/exit() + wait()

// 线程的退出方式
workerThread->quit();
workerThread->wait();

如果QThread对象在堆内存区创建:

1.有父对象:可通过finished信号,连接deleteLater来让线程自杀。

WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);

2.没有父对象:在主界面的析构函数或者通过destroyed信号,显式地释放堆内存。

// 析构函数
~MainWindow() 
{
    workerThread->quit();
	workerThread->wait();
	workerThread->deleteLater();
}

// destroyed信号
connect(this, &MainWindow::destroyed, this, [=]()
{
	workerThread->quit();
	workerThread->wait();
	workerThread->deleteLater();
});

3. 线程同步

3.1 互斥量

当出现多个线程需要操作同一个资源时,互斥量可用于保护一个资源一次仅被一个线程使用,Qt 中使用QMutexQMutexLocker实现。

QMutex mutex;
int number = 6;

void thread1()
{
    // 锁定互斥量
    mutex.lock();
    number *= 5;
    number /= 4;
    // 解锁互斥量
    mutex.unlock();
}

void thread2()
{
    mutex.lock();
    number *= 3;
    number /= 2;
    mutex.unlock();
}

QMutexLocker可以简化互斥量的处理,仅需在需要互斥量保护的函数中声明一个对象,函数结束时会自动对互斥量解锁。

void func()
{
    QMutexLocker locker(&mutex);
    /* do something */
}

3.2 信号量

信号量是互斥量的一般化,互斥量只能保护一个资源,而信号量可以用来保护一定数量的相同的资源,Qt 中使用QSemaphore类实现,常用的接口为:

接口功能
void acquire(int n = 1)获取n个资源(默认值1),当没有足够资源时调用者被阻塞,可用资源数量-n
void release(int n = 1)释放n个资源(默认值1),可用资源数量+n
int available() const返回当前可用资源的数量

信号量的典型用例是控制生产者/消费者之间共享的环形缓冲区,生产者/消费者对同步的需求为:

(1) 如果生产者过快地产生数据,就会覆盖消费者还没读取的数据。

(2) 如果消费者过快地读取数据,就会读取到生产者之前产生的过期数据。

可将生产者和消费者分为2个独立的线程,用2种信号量分别控制缓冲区中可写和可读的部分,可省略传统环形缓冲区中的读/写指针,简单有效。

全局变量存放在 global.h 和 global.cpp 中,使用时只需#include "global.h"即可:

/*---------------------------global.h----------------------------*/
extern const quint32 BUFFERSIZE;
extern const quint32 DATASIZE;
extern char g_szBuffer[BUFFERSIZE];
extern QSemaphore freeBytes;
extern QSemaphore usedBytes;

/*---------------------------global.cpp----------------------------*/
#include "global.h"

const quint32 BUFFERSIZE = 20;
const quint32 DATASIZE = 40;
char g_szBuffer[BUFFERSIZE]{'\0'}; //缓冲区数组
QSemaphore freeBytes(BUFFERSIZE); //缓冲区可写资源, 初始化数量为BUFFERSIZE
QSemaphore usedBytes; //缓冲区可读资源, 初始化数量为0

生产者线程(这里采用官方推荐的worker-object方法实现):

class CProducer : public QObject
{
    Q_OBJECT
public:
    explicit CProducer(QObject *parent = nullptr);

public slots:
    void produce();
};

void CProducer::produce()
{
    while(num < DATASIZE)
    {
        static quint32 num = 0; //生产资源总数
        quint16 len = QRandomGenerator::global()->bounded(BUFFERSIZE) + 1; //生产者一次生产的资源是随机的, 范围[1, BUFFERSIZE]
        freeBytes.acquire(len);
        for (auto i = num; i < num + len; ++i)
        {
            g_szBuffer[i % BUFFERSIZE] = QRandomGenerator::global()->bounded(100); //[i % BUFFERSIZE]是实现环形缓冲区的精髓
        }
        usedBytes.release(len);
        num += len;
    }
}

消费者线程:

class CConsumer : public QObject
{
    Q_OBJECT
public:
    explicit CConsumer(QObject *parent = nullptr);

public slots:
    void consume();

private:
    bool m_stopflag = false;
    const quint16 CONSUME_DATASIZE = 20; //消费者一次消费的资源数量最大值
    std::string charToHexString(const char& ch);
};

void CConsumer::consume()
{
    while(num < DATASIZE)
    {
        static quint32 num = 0;
        quint16 len = 0;
        if((usedBytes.available() - CONSUME_DATASIZE) >= 0) //确保消费的资源数量不大于缓冲区内可读的资源数量
        {
            len = CONSUME_DATASIZE;
        }
        else
        {
            len = (usedBytes.available() == 0)? 1 : usedBytes.available(); //保证acquire的数量不为0
        }
        usedBytes.acquire(len);
        for (auto i = num; i < num + len; ++i)
        {
            std::cout << charToHexString(g_szBuffer[i % BUFFERSIZE]) << std::endl;
        }
        freeBytes.release(len);
        num += len;
    }
}

main.cpp:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    CProducer producer;
    QThread producerThread;
    producer.moveToThread(&producerThread);
    QObject::connect(&producerThread, &QThread::started, &producer, &CProducer::produce);

    CConsumer consumer;
    QThread consumerThread;
    consumer.moveToThread(&consumerThread);
    QObject::connect(&consumerThread, &QThread::started, &consumer, &CConsumer::consume);

    producerThread.start();
    consumerThread.start();
    producerThread.wait();
    consumerThread.wait();
    producerThread.quit();
    consumerThread.quit();
    return a.exec();
}

3.3 条件变量

条件变量用于阻塞和唤醒线程,Qt中使用QWaitCondition实现,常用的接口为:

接口功能
bool wait(QMutex *lockedMutex, QDeadlineTimer deadline = QDeadlineTimer(QDeadlineTimer::Forever))解锁互斥量lockedMutex并阻塞当前线程
void wakeOne()唤醒一个随机选取的被该条件变量阻塞的线程
void wakeAll()唤醒所有被该条件变量阻塞的线程

当调用条件变量的wait()方法时,线程进入阻塞状态,直到该条件变量调用wakeOne()或者wakeAll(),被阻塞的线程才被唤醒。

使用条件变量也可以实现生产者/消费者模式:

全局变量:

/*---------------------------global.h----------------------------*/
extern const quint16 BUFFERSIZE;
extern const quint32 DATASIZE;
extern char g_szBuffer[BUFFERSIZE];
extern QWaitCondition bufferNotEmpty;
extern QWaitCondition bufferNotFull;
extern QMutex g_mutex;
extern quint16 g_usedBytesNum;

/*---------------------------global.cpp----------------------------*/
#include "global.h"

const quint16 BUFFERSIZE = 20;
const quint32 DATASIZE = 40;
char g_szBuffer[BUFFERSIZE]{'\0'}; //缓冲区数组
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex g_mutex; //使用互斥量保证原子性
quint16 g_usedBytesNum = 0; //可读资源数量

生产者线程:

class CProducer : public QObject
{
    Q_OBJECT
public:
    explicit CProducer(QObject *parent = nullptr);

public slots:
    void produce();
};

void CProducer::produce()
{
    while(num < DATASIZE)
    {
        static quint32 num = 0;
        g_mutex.lock();
        if (g_usedBytesNum == BUFFERSIZE) //缓冲区被写满时触发bufferNotFull.wait()
            bufferNotFull.wait(&g_mutex);
        g_mutex.unlock();

        quint16 len = QRandomGenerator::global()->bounded(10);
        for (auto i = num; i < num + len; ++i)
        {
            g_szBuffer[i % BUFFERSIZE] = QRandomGenerator::global()->bounded(100);
        }
        num += len;

        g_mutex.lock();
        g_usedBytesNum += len;
        bufferNotEmpty.wakeAll(); //唤醒消费者线程
        g_mutex.unlock();
    }
}

消费者线程:

class CConsumer : public QObject
{
    Q_OBJECT
public:
    explicit CConsumer(QObject *parent = nullptr);

public slots:
    void consume();

private:
    bool m_stopflag = false;
    const quint16 CONSUME_DATASIZE = 20; //消费者一次消费的资源数量最大值

    std::string charToHexString(const char& ch);
};

void CConsumer::consume()
{
    while(num < DATASIZE)
    {
        g_mutex.lock();
        if (g_usedBytesNum == 0) //缓冲区读取完毕时触发bufferNotEmpty.wait()
            bufferNotEmpty.wait(&g_mutex);
        g_mutex.unlock();

        static quint32 num = 0;
        quint16 len = 0;
        if((g_usedBytesNum - CONSUME_DATASIZE) >= 0)
        {
            len = CONSUME_DATASIZE;
        }
        else
        {
            len = g_usedBytesNum;
        }

        for (auto i = num; i < num + len; ++i)
        {
            std::cout << charToHexString(g_szBuffer[i % BUFFERSIZE]) << std::endl;
        }
        num += len;

        g_mutex.lock();
        g_usedBytesNum -= len;
        bufferNotFull.wakeAll(); //唤醒生产者线程
        g_mutex.unlock();
    }
}

4. 线程池

通常情况下,一个并发的任务就会创建一个新的线程,但如果并发的线程数量很多,且线程执行时间不同步,频繁创建线程就会大大降低系统的效率,此时可以使用线程池来替我们管理线程。使用线程池有以下优点:

  • 线程池可以根据系统的需求和硬件环境灵活的控制线程并发的数量,且可以对所有线程进行统一的管理和控制。
  • 线程和任务分离,可以提升线程的重用性。
  • 提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间。

Qt中使用线程池比较方便,主要用到QRunnableQThreadPool类。其中QRunnable是添加到线程池的任务类,使用时需要创建子类继承 QRunnable ,然后重写run()方法,在这个函数内编写要执行的任务。

class CTask : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit CProducer(QObject *parent = nullptr): QObject{parent}, QRunnable()
    {
    	//任务执行完毕,该对象自动销毁
    	setAutoDelete(true);
    }

    void run() override{
        /* do something */
    }
};

一般情况下,在主线程中不需要创建线程池对象,直接使用QThreadPool::globalInstance()获得线程池全局对象即可。通过调用全局对象的start()方法就可以将一个任务添加到线程池中,这样任务就可以被线程池中的某个工作的线程处理掉了。

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 线程池初始化,设置最大线程数
    QThreadPool::globalInstance()->setMaxThreadCount(4);
    // 添加任务
    CTask* pTask = new CTask;
    QThreadPool::globalInstance()->start(pTask);    
}

参考资料

QThread Class

Qt connect

QMutex Class

QSemaphore Class

QWaitCondition Class

QT信号和槽在哪个线程执行问题

Qt - 一文理解QThread多线程(万字剖析整理)

Qt 中多线程的使用

Qt 中线程池的使用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt学习笔记:多线程的使用 的相关文章

  • cnn-过拟合(over-fitting)

    概念 为了得到一致假设而使假设变得过度严格称为过拟合 1 给定一个假设空间H xff0c 一个假设h属于H xff0c 如果存在其他的假设h 属于H 使得在训练样例上h的错误率比h 小 xff0c 但在整个实例分布上h 比h的错误率小 xf
  • cnn-欠拟合(underfitting)

    模型不能很好拟合数据 称之为欠拟合 直白的说 xff1a 模型没有找到数据规律或不完整 xff0c 泛化能力不强 在训练和测试数据集上 xff0c 预测或训练结果都和真实结果相差很远 一般解决方法 增加新特征 xff0c 可以考虑加入进特征
  • ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full comm

    类似这种问题 xff0c 不一定是pip版本不对 xff0c 有可能是某个文件不存在 xff0c 例如 在python3 5环境中安装scikit image pip install scikit image 61 61 0 12 就出现
  • AI最新资讯,持续更新

    三星 人造人 项目曝光 xff01 效果太逼真 xff0c 可自主生成新表情 动作和对话 https mp weixin qq com s 417fL3oYVE1vOwsVHMmqow Det3D 首个通用 3D 目标检测框架 https
  • 二维码生成以及扫一扫解析二维码原理

    二维码生成以及扫一扫解析二维码原理 1 生成URL xff0c 确定要通过二维码传达的信息 xff0c 也就是通过扫一扫可以获得地址和数据信息 1 得到随机数 xff0c 用随机数得到签名 xff0c 签名验证身份 String ranSt
  • idea 不能生成target

    1 改module https blog csdn net qq 15304369 article details 93715206 2 pom配置文件 修改为 xff1a lt packaging gt jar lt packaging
  • mariadb 遇到的坑

    mariadb13 3 25 配置文件失效 xff08 折腾了很久 xff09 xff0c 当时我需要配置主从 xff0c 发现binlog无法打开 xff0c 配置了bin log项还是不行 xff01 当my cnf 文件权限过大时 x
  • CV资料汇总

    1 图像风格迁移 Neural Style 简史 https www sohu com a 221597595 236505 2 一文让你理解什么是卷积神经网络 https www jianshu com p 1ea2949c0056
  • skinmagic 对话框菜单展示

    我偶用skinmagic xff0c 在换对话框皮肤时候 xff0c 发现菜单不见了 xff0c 几经折腾 xff0c 发现SetWindowSkin m hWnd 34 Dialog 34 在iniInstance xff08 xff09
  • 系统如何支持高并发

    给个例子 xff0c 你的系统部署的机器是4核8G xff0c 数据库服务器是16核32G 此时假设你的系统用户量总共就10万 xff0c 用户量很少 xff0c 日活用户按照不同系统的场景有区别 xff0c 我们取一个较为客观的比例 xf
  • Firewalld防火墙基础

    目录 一 Firewalld 概述 1 1 Firewalld的简述 1 2 Firewalld 和 iptables的区别 1 3 firewalld的区域 1 3 1 firewalld的9个区域 1 3 2 firewalld的数据处
  • CentOS7安装Oracle JDK

    CentOS7默认安装的是OpenJDK 如果安装Oracle JDK xff0c 需要按如下方式操作 xff1a 1 登录http www oracle com technetwork java javase downloads inde
  • 百度2014校招笔试题(一)

    算法和程序设计题 xff1a 1 题意 xff1a 一幢大楼的底层有1001根电线 xff0c 这些电线一直延伸到大楼楼顶 xff0c 你需要确定底层的1001个线头和楼顶的1001次线头的对应关系 你有一个电池 xff0c 一个灯泡 xf
  • Acwing 1175.最大联通子图(tarjan缩点求scc)

    Acwing 1175 最大连通子图 题意 一个有向图 G 61 V E G 61 V
  • 用github搭建个人(博客网站

    x1f308 博客主页 xff1a 卿云阁 x1f48c 欢迎关注 x1f389 点赞 x1f44d 收藏 留言 x1f4dd x1f31f 本文由卿云阁原创 xff01 x1f64f 作者水平很有限 xff0c 如果发现错误 xff0c
  • 多线程下HashMap的死循环

    多线程下HashMap的死循环 Java的HashMap是非线程安全的 多线程下应该用ConcurrentHashMap 多线程下 HashMap 的问题 xff08 这里主要说死循环问题 xff09 xff1a 1 多线程put操作后 x
  • 找出一个图中所有的强连通子图

    如果一个有向图中的没对顶点都可以从通过路径可达 xff0c 那么就称这个图是强连通的 一个 strongly connected component就是一个有向图中最大的强连通子图 下图中就有三个强连通子图 xff1a 应用kosaraju
  • win7启动分区不存在,使用分区工具修正

    DiskGenius 分区右键 激活当前分区
  • getElementById获取不到td标签

    一次测试中发现 然后使用getElementById获取不到此标签 xff0c 将td改成div即可 不知道是不是单独使用td标签的问题 code
  • 应用宝YSDK支付接入技术细节

    前言 应用宝是出了名的坑 xff0c 主要体现在 xff1a 文档杂乱繁多信息不全或描述模糊文档格式不规范技术支持很不及时 并且可以明显察觉到为了兼容QQ和微信 xff0c 应用宝的接入规范有诸多不合理的地方 来来回回折腾了一周 xff0c

随机推荐

  • 用Word2007批量设置图片位置

    转自 xff1a http www ccw com cn college htm2010 20100727 877695 shtml Word2007的 查找和替换 功能并不仅仅可以对文字进行批量的查找替换 xff0c 还有很多神奇的功能
  • java-生产者消费者问题以及解决办法

    文章目录 1 生产者消费者问题概述2 生产者消费者问题的解决办法2 1 解决思路2 2 实现方法2 3 代码实现2 3 1 wait 和nofity 方法2 3 2 await signal 方法2 3 3 BlockingQueue阻塞队
  • 【Remote Development】VSCode 基于 SSH 进行远程开发

    系统需求 我们在 VSCode 下载由微软官方推出的 Remote SSH 插件 查看一下里面的描述 xff0c 对于远程机器的要求如下 xff1a Local A supported OpenSSH compatible SSH clie
  • git idea创建新分支,获取/合并主支代码的2个方法

    其他sql格式也在更新中 xff0c 可直接查看这个系列 xff0c 要是没有你需要的格式 xff0c 可在评论或私信我 个人目录 获取主支代码的2个方法 1 xff0c 创建一个分支 xff0c 获取主支的所有代码 xff08 场景 xf
  • spring手把手超详细讲解(基本配置,基于xml)

    spring教程 1 1 容器概述1 1 1 配置元数据1 1 2 容器的实例化1 1 3 容器的使用 1 2 bean的概述1 2 1 命名bean1 2 2 实例化Bean 1 3 依赖1 3 1 依赖注入1 3 2 使用 属性1 3
  • 18.5 重载全局new、delete、定位new及重载等

    一 xff1a 重载全局operator new和operator delete操作符 span class token macro property span class token directive hash span span cl
  • java进程占用CPU过高常见的两种情况及分析定位

    java进程爆cpu的快速定位 1 背景 在程序开发的过程中 xff0c 难免遇到进程占用cpu过高 xff08 现网居多 开发环境 xff09 的情况 xff0c 现网出现这种情况就需要及时的能定位到问题 xff0c 快速解决 xff0c
  • 【Android ViewBinding】内存泄露

    场景 在MainActivity中分别加载两个Fragment处理业务 首先触发加载SecondFragment xff1a MainActivity触发 supportFragmentManager commit add R id con
  • Shell小脚本实现一键关机/重启虚拟机

    利用Shell脚本实现一键关机 重启虚拟机 xff0c 解决每次虚拟机关机或重启都需要手动一个个关机或重启的烦恼 xff01 1 脚本一 xff1a shut sh span class token comment bin bash spa
  • LAMP环境搭建

    前言 一 在虚拟机上安装Linux系统 二 安装Apache 1 下载好后 xff0c 看了看版本 xff0c 不是太老 xff0c 就没有继续安装 2 开启Apache服务 3 设置Apache开机启动服务 4 尝试一下是否启动了服务 x
  • 小程序跳坑之安卓真机不能访问服务器的问题

    因为一项目 xff0c 有几个页面都需要访问服务器 xff0c 从服务器上下载数据 xff0c 在苹果和开发者工具上都运行完美 xff0c 唯独一款安卓手机 xff0c 访问不了 xff0c 经测试 xff0c 发现是汉字编码问题 xff0
  • python Tkinter 界面button调用多进程函数,弹出多个相同界面

    这是我的界面button command的函数start simulate 这是我的多进程函数 xff1a 点击之后 xff0c 弹出多个相同界面 把调用多进程的函数在 if name 61 61 39 main 39 这里调用就不会出现多
  • python入门之if-else语句

    文章目录 一 if语句二 elif语句三 if嵌套语句四 else语句1五 else语句2六 if else语句举例1七 if else语句举例2 一 if语句 span class token keyword if span False
  • Ubuntu 16.04 远程桌面

    1 安装xrdp sudo apt get install xrdp 2 安装vnc4server 我这里是安装xrdp的时候自动安装的 我看网上很多说是需要单独安装的 3 安装xfce4 sudo apt get install xubu
  • GitLab端口冲突 解决办法

    访问gitlab xff0c 出现 xff1a 502 GitLab在使用的过程中 xff0c 会开启80端口 xff0c 如果80端口被其他的应用程序占用 xff0c 则GitLab的该项服务不能使用 xff0c 所以访问GitLab会失
  • Android开发 之 确认凭证

    确认凭证 主要目的 xff1a 设置不用验证时间 设置为30秒 xff0c 当超过30秒后则需要重新验证身份才能操作 您的应用可以根据用户在多久之前最后一次解锁设备来验证其身份 此功能让用户不必费心记忆应用特定密码 xff0c 您也无需实现
  • inner join、outer join、right join、left join 之间的区别

    inner join outer join right join left join 之间的区别 一 sql的left join right join inner join之间的区别 left join 左联接 返回包括左表中的所有记录和右
  • "大泥球"仍然是最常见的软件设计

    大泥球 xff0c 是指杂乱无章 错综复杂 邋遢不堪 随意拼贴的大堆代码 这些年来 xff0c 为了对付这个泥球 xff0c 我们看到了多种指导方法 xff0c 比如SOLID GRASP 和KISS xff0c 与其他诸多年代久远的 提倡
  • 3322.org带来的麻烦

    大概是3322 org被短时间攻破 xff0c 下载他的动态域名客户端的时候下到一个病毒Trojandropper js adagent gd xff0c 把江民关了 xff0c 并且再也开不开 系统还原不行 xff0c 安全模式也进不去
  • Qt学习笔记:多线程的使用

    文章目录 前言1 何时使用线程2 QThread类实现多线程2 1 多线程的实现方法2 2 线程休眠2 3 正确结束线程 3 线程同步3 1 互斥量3 2 信号量3 3 条件变量 4 线程池参考资料 前言 程序中调用耗时的操作 xff08