当前位置: 首页 > news >正文

C++客户端Qt开发——多线程编程(二)

多线程编程(二) 

③线程池

Qt中线程池的使用 | 爱编程的大丙

1>线程池

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

2>线程池原理

在各个编程语言的语种中都有线程池的概念,并且很多语言中直接提供了线程池,作为程序猿直接使用就可以了,下面介绍一下线程池的实现原理:

线程池的组成主要分为3个部分,这三部分配合工作就可以得到一个完整的线程池:

1. 任务队列,存储需要处理的任务,由工作的线程来处理这些任务

  • 通过线程池提供的API函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
  • 已处理的任务会被从任务队列中删除
  • 线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程

2. 工作的线程(任务队列任务的消费者),N个

  • 线程池中维护了一定数量的工作线程,他们的作用是是不停的读任务队列,从里边取出任务并处理
  • 工作的线程相当于是任务队列的消费者角色,
  • 如果任务队列为空,工作的线程将会被阻塞(使用条件变量/信号量阻塞)
  • 如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作

3. 管理者线程(不处理任务队列中的任务),1个

  • 它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测
    • 当任务过多的时候可以适当的创建一些新的工作线程
    • 当任务过少的时候可以适当的销毁一些工作的线程

3>线程池的使用QRunnable

在Qt中使用线程池需要先创建任务,添加到线程池中的每一个任务都需要是一个QRunnable类型,因此在程序中需要创建子类继承QRunnable这个类,然后重写run()方法,在这个函数中编写要在线程池中执行的任务,并将这个子类对象传递给线程池,这样任务就可以被线程池中的某个工作的线程处理掉了。

QRunnable类常用函数不多,主要是设置任务对象传给线程池后,是否需要自动析构。

[pure virtual] void QRunnable::run();

在子类中必须要重写的函数, 里边是任务的处理流程

void QRunnable::setAutoDelete(bool autoDelete);

  • 参数设置为 true: 这个任务对象在线程池中的线程中处理完毕, 这个任务对象就会自动销毁
  • 参数设置为 false: 这个任务对象在线程池中的线程中处理完毕, 对象需要手动销毁

bool QRunnable::autoDelete() const;

获取当前任务对象的析构方式,返回true->自动析构, 返回false->手动析构

创建一个要添加到线程池中的任务类,处理方式如下:

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

在上面的示例中MyWork类是一个多重继承,如果需要在这个任务中使用Qt的信号槽机制进行数据的传递就必须继承Qobject这个类,如果不使用信号槽传递数据就可以不继承了,只继承QRunnable即可

class MyWork :public QRunnable
{Q_OBJECT
public:explicit MyWork(){// 任务执行完毕,该对象自动销毁setAutoDelete(true);}~MyWork();void run() override{};
}
4>QThreadPool

Qt中的QThreadPool类管理了一组QThreads,里边还维护了一个任务队列。QThreadPool管理和回收各个QThread对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局QThreadPool对象,可以通过调用globalInstance()来访问它。也可以单独创建一个QThreadPool对象使用。

线程池常用的API函数如下:

int maxThreadCount() const;

void setMaxThreadCount(int maxThreadCount);

获取和设置线程中的最大线程个数

void QThreadPool::start(QRunnable * runnable, int priority = 0);

给线程池添加任务, 任务是一个 QRunnable 类型的对象,如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理

bool QThreadPool::tryStart(QRunnable * runnable);

如果线程池中没有空闲的线程了, 直接返回值, 任务添加失败, 任务不会添加到任务队列中

int QThreadPool::activeThreadCount() const;

线程池中被激活的线程的个数(正在工作的线程个数)

bool QThreadPool::tryTake(QRunnable *runnable);

尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了

void QThreadPool::clear();

将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了

static QThreadPool * QThreadPool::globalInstance();

在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象

一般情况下,我们不需要在Qt程序中创建线程池对象,直接使用Qt为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用start()方法就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。

使用示例:

class MyWork :public QRunnable
{Q_OBJECT
public:explicit MyWork();~MyWork();void run() override;
}
MyWork::MyWork() : QRunnable()
{// 任务执行完毕,该对象自动销毁setAutoDelete(true);
}
void MyWork::run()
{// 业务处理代码......
}
MyWork::MyWork() : QRunnable()
{// 任务执行完毕,该对象自动销毁setAutoDelete(true);
}
void MyWork::run()
{// 业务处理代码......
}
示例:生成随机数,然后使用冒泡排序和快速排序对其进行处理

这个示例再用线程池实现一次

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QThread>
#include<QVector>
#include<QRunnable>//生成随机数,将构造类的名字直接改成Generate更明确一些
class Generate :public QObject, public QRunnable
{Q_OBJECT
public:explicit Generate(QObject *parent = nullptr);//将主线程传递过来的数保存到m_numvoid recvNum(int num);void run() override;signals:void sendArray(QVector<int>);private:int m_num;};class BubbleSort :public QObject, public QRunnable
{Q_OBJECT
public:explicit BubbleSort(QObject *parent = nullptr);//将主线程传递过来的数保存到m_numvoid recvArray(QVector<int> list);void run() override;signals:void finish(QVector<int>);private:QVector<int> m_list;};class QuickSort :public QObject, public QRunnable
{Q_OBJECT
public:explicit QuickSort(QObject *parent = nullptr);//将主线程传递过来的数保存到m_numvoid recvArray(QVector<int> list);void run() override;private:void quickSort(QVector<int> &list,int l, int r);signals:void finish(QVector<int>);private:QVector<int> m_list;};#endif // MYTHREAD_H

 mythread.cpp

#include "mythread.h"
#include<QElapsedTimer>
#include<QDebug>
#include<QThread>Generate::Generate(QObject *parent) :QObject(parent),QRunnable()
{//设置自动析构setAutoDelete(true);
}void Generate::recvNum(int num)
{m_num = num;
}void Generate::run()
{qDebug() << "生成随机数的线程的线程地址:" << QThread::currentThread();QVector<int> list;//计时QElapsedTimer time;time.start();for(int i=0; i<m_num; ++i){list.push_back(qrand() % 100000);}int milsec = time.elapsed();qDebug() << "生成" << m_num << "随机数总用时:" << milsec << "毫秒";//发送给主线程emit sendArray(list);
}BubbleSort::BubbleSort(QObject *parent):QObject(parent),QRunnable()
{//设置自动析构setAutoDelete(true);
}void BubbleSort::recvArray(QVector<int> list)
{m_list = list;
}void BubbleSort::run()
{qDebug() << "冒泡排序的线程的线程地址:" << QThread::currentThread();//计时QElapsedTimer time;time.start();//冒泡排序int temp;for(int i=0;i<m_list.size();++i){for(int j=0;j<m_list.size()-i-1;++j){if(m_list[j] > m_list[j+1]){temp = m_list[j];m_list[j] = m_list[j+1];m_list[j+1] = temp;}}}int milsec = time.elapsed();qDebug() << "冒泡排序用时:" << milsec << "毫秒";emit finish(m_list);
}QuickSort::QuickSort(QObject *parent):QObject(parent),QRunnable()
{//设置自动析构setAutoDelete(true);
}void QuickSort::recvArray(QVector<int> list)
{m_list = list;
}void QuickSort::run()
{qDebug() << "快速排序的线程的线程地址:" << QThread::currentThread();//计时QElapsedTimer time;time.start();//快速排序quickSort(m_list,0,m_list.size()-1);int milsec = time.elapsed();qDebug() << "快速排序用时:" << milsec << "毫秒";emit finish(m_list);
}void QuickSort::quickSort(QVector<int> &s, int l, int r)
{if(l<r){int i = l,j = r;int x = s[l];while(i < j){while(i < j && s[j] >= x){j--;}if(i < j){s[i++] = s[j];}while(i < j && s[i] < x){i++;}if(i < j){s[j--] = s[i];}}s[i] = x;quickSort(s,l,i-1);quickSort(s,i+1,r);}
}

 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();signals:void starting(int num);private:Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

 mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<mythread.h>
#include<QThreadPool>
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//1.创建任务对象Generate* gen = new Generate;BubbleSort* bubble = new BubbleSort;QuickSort* quick = new QuickSort;connect(this,&MainWindow::starting,gen,&Generate::recvNum);//2.启动子线程connect(ui->start,&QPushButton::clicked,this,[=](){emit starting(10000);QThreadPool::globalInstance()->start(gen);});//随机数子线程发送来的数据触发冒泡排序和快速排序接收数据connect(gen,&Generate::sendArray,bubble,&BubbleSort::recvArray);connect(gen,&Generate::sendArray,quick,&QuickSort::recvArray);connect(gen,&Generate::sendArray,this,[=](QVector<int> list){//启动两个任务QThreadPool::globalInstance()->start(bubble);QThreadPool::globalInstance()->start(quick);for (int i=0; i<list.size(); ++i) {ui->randlist->addItem(QString::number(list.at(i)));}});//两个排序处理数据connect(bubble,&BubbleSort::finish,this,[=](QVector<int> list){for (int i=0; i<list.size(); ++i) {ui->bubblelist->addItem(QString::number(list.at(i)));}});connect(quick,&QuickSort::finish,this,[=](QVector<int> list){for (int i=0; i<list.size(); ++i) {ui->quicklist->addItem(QString::number(list.at(i)));}});
}MainWindow::~MainWindow()
{delete ui;
}

main.cpp 

#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);qRegisterMetaType<QVector<int>>("QVector<int>");MainWindow w;w.show();return a.exec();
}

④线程安全

实现线程互斥和同步常用的类有:

  • 互斥锁:QMutex、QMutexLocker
  • 条件变量:QWaitCondition
  • 信号量:QSemaphore
  • 读写锁:QReadLocker、QWriteLocker、QReadWriteLock

多个线程加锁的对象,得是同一个对象,不同对象,此时不会产生锁的互斥,也就无法把并发执行变为串行执行

1>互斥锁

互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法,在Qt中,互斥锁主要是通过QMutex类来处理。

  • OMutex

        特点:QMutex是Qt框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。

        用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全

QMutex mutex;mutex.lock();//上锁
//访问共享资源
//……
mutex.unlock();//解锁
  • OMutexLocker

        特点:OMutexLockerQMutex的辅助类,使用RAII(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作。

        用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题

QMutex mutex;
{QMutexLocker locker(&mutex);//在作用域内自动上锁//访问共享资源//...
}//在作用域结束时自动解锁
  1. QMutex mutex; 创建了一个 QMutex 对象。
  2. 在大括号 {} 内,创建了 QMutexLocker 的实例 locker,它接受 mutex 的地址作为参数。这将锁定 mutex
  3. 在大括号内,可以安全地访问共享资源,因为互斥量已经被锁定,防止了其他线程同时访问。
  4. 当离开大括号的作用域时,locker 的实例被销毁,它的析构函数会自动调用 mutex.unlock(),从而释放互斥量。
  • QReadWriteLockerQReadLockerQWriteLocker

特点:

  • QReadWriteLock读写锁类,用于控制读和写的并发访问。
  • QReadLocker用于读操作上锁,允许多个线程同时读取共享资源。
  • QWriteLocker用于写操作上锁,只允许一个线程写入共享资源。

用途:在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式。

OReadWriteLock rwLock;
//在读操作中使用读锁
{QReadLocker locker(&rwLock);//在作用域内自动上读锁//读取共享资源//……
}//在作用域结束时自动解读锁//在写操作中使用写锁
{QWriteLocker locker(&rwLock);//在作用域内自动上写锁//修改共享资源//……
}//在作用域结束时自动解写锁
2>条件变量

在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件满足才能执行,这时就会出现问题。这种情况下,线程会很自然地使用锁的机制来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进入了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另一个线程唤醒。

在Qt中,专门提供了QWaitCondition类来解决像上述这样的问题。

特点:QWaitCondition是Qt框架提供的条件变量类,用于线程之间的消息通信和同步。

用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。

3>信号量

有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量

特点:QSemaphore是Qt框架提供的计数信号量类,用于控制同时访问共享资源的线程数量。

用途:限制并发线程数量,用于解决一些资源有限的问题。

相关文章:

C++客户端Qt开发——多线程编程(二)

多线程编程&#xff08;二&#xff09; ③线程池 Qt中线程池的使用 | 爱编程的大丙 1>线程池 我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a;如果并发的线程数量很多&#xff0c;并且每个线程都是执行…...

ubuntu20复现NBV探索

官网代码 后退地平线下一个最佳景观规划师 这个代码有些久远&#xff0c;issue里面有人已经在ubuntu20里面使用了3dmr&#xff0c;但是他那个代码我也运行不成功&#xff0c;docker网络一直也不佳&#xff0c;所以还是自己重新修改源码靠谱。 最终实现的代码等有时间上传到gi…...

【51单片机仿真】基于51单片机设计的温湿度采集检测系统仿真源码文档视频——文末资料下载

演示 目录 1.系统功能 2.背景介绍 3.硬件电路设计 4.软件设计 4.1 主程序设计 4.2 温湿度采集模块程序设计 4.3 LCD显示屏程序设计 5.系统测试 6.结束语 源码、仿真、文档视频等资料下载链接 1.系统功能 该系统通过与AT89C51单片机、LCD1602显示屏和DHT11温湿度传感器…...

【Hadoop-驯化】一文学会hadoop访问hdfs中常用命令使用技巧

【Hadoop-驯化】一文学会hadoop访问hdfs中常用命令使用技巧 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内容文档关注&am…...

【Spring】Bean详细解析

1.Spring Bean的生命周期 整体上可以简单分为四步&#xff1a;实例化 —> 属性赋值 —> 初始化 —> 销毁。初始化这一步涉及到的步骤比较多&#xff0c;包含 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBean 和 init-method 的初始…...

决策树总结

决策树总结 决策树是一种广泛应用的机器学习算法&#xff0c;它模拟了人类进行决策时的逻辑思维过程&#xff0c;通过构建一棵树状结构来进行数据的分类或回归预测。决策树模型因其直观易懂、易于解释、能够处理多类问题以及无需进行复杂的特征缩放等优点&#xff0c;在数据挖…...

通俗易懂!495页看漫画学Python入门教程(全彩版)Git首发破万Star

前言 在编程的世界里&#xff0c;Python无疑是一颗璀璨的明星。从最初作为打发圣诞节闲暇时间的项目&#xff0c;到如今成为最受欢迎的程序设计语言之一&#xff0c;Python以其简洁、易学、强大的特点吸引了无数编程爱好者。然而&#xff0c;对于初学者来说&#xff0c;编程的…...

websocket实现简易聊天室

websocket实现简易聊天室 又做了一个关于websocket广播和在线人数统计的练习&#xff0c;实现一个简易的聊天室。 前端vue3 前端里的内容主要包含&#xff1a; 1.css的animation来实现公告从右到左的轮播。 2.websocket的onmessage里对不同消息的处理。 <template>&l…...

vulhub-wordpress

1.打开wordpress关卡&#xff0c;选择简体中文 添加信息——点击安装WordPress 安装完成——登录 点击外观——编辑主题 可以加入一句话木马&#xff0c;但是我写入的是探针文件 也可以去上传一个带有木马的主题 上传之后会自动解压 1.php就是里面的木马文件...

【机器学习算法基础】(基础机器学习课程)-10-逻辑回归-笔记

一、模型的保存与加载 逻辑回归是一种常见的机器学习算法&#xff0c;广泛用于分类问题。为了在不同的时间或环境下使用训练好的模型&#xff0c;我们通常需要将其保存和加载。 保存模型 训练模型&#xff1a;首先&#xff0c;你需要用你的数据训练一个逻辑回归模型。例如&…...

自动驾驶行业知识汇总

应届生月薪2W的自动驾驶开发、机器人、后端开发&#xff0c;软件开发该如何学习相关技术栈_哔哩哔哩_bilibili 两万字详解自动驾驶开发工具链的现状与趋势 (qq.com) 九章智驾 - 2023年度文章大合集 (qq.com) 九章 - 2022年度文章大合集 (qq.com)...

C#根据反射操作对象

前言 反射使用&#xff0c;让我们的程序可以动态增加一些功能&#xff0c;让原本固化的步骤逻辑变得动态&#xff0c;这是它的优点。当然使用反射首次加载会有性能损耗以及使用复杂&#xff1b;但是现在大家都在讲动态&#xff0c;使用好它应该是一个重要的编程理念提升。MVC、…...

打包python脚本(flask、jinja2)为exe文件

20240803 概述 在我很早时候学习python的时候&#xff0c;就利用过某个工具将其打包为exe文件&#xff0c;然后在没有python环境的机器上运行&#xff0c;这样可以减少安装python环境和各种库的过程。 最近在开发一个在虚拟机上运行的程序的时候就遇到了打包一些环境的问题&…...

嵌入式初学-C语言-练习三

#部分题目可能在之前的博客中有&#xff0c;请谅解&#xff0c;保证常见题型均被发出# 1.计算n以内所有正奇数的和 ? n值通过键盘输入 代码&#xff1a; 1 /*2 需求&#xff1a;计算n以内所有正奇数的和 ? n值通过键盘输入3 */4 #include <stdio.h>5 6 int main()7 …...

最新版Sonible Plugins Bundle v2024 winmac,简单智能,持续更新长期有效

一。Sonible Plugins Bundle v2024 win&mac Sonible Plugins Bundle是一款以创作者为中心的智能音频插件系列。这些工具的特点是易于使用&#xff0c;搭配高级处理和优质音质。pure:bundle的所有插件都由sonible的智能插件系列中使用的技术驱动&#xff0c;但在设计时考虑到…...

J032_实现简易版的B/S架构

一、需求描述 实现简易版的B/S架构 1.1 Server package com.itheima.tcp4;import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.uti…...

【前端面试】五、框架

目录 1 Vue2 2 Vue3 3 React 4 Angular 1 Vue2 Vue2 是目前仍被广泛使用的前端框架之一&#xff0c;其特点包括响应式数据绑定、组件化开发等。 响应式系统&#xff1a;Vue2 使用 Object.defineProperty 来实现数据的响应式。每个组件实例在创建时&#xff0c;会将 dat…...

C语言 | Leetcode C语言题解之第316题去除重复字母

题目&#xff1a; 题解&#xff1a; char* removeDuplicateLetters(char* s) {int vis[26], num[26];memset(vis, 0, sizeof(vis));memset(num, 0, sizeof(num));int n strlen(s);for (int i 0; i < n; i) {num[s[i] - a];}char* stk malloc(sizeof(char) * 27);int stk…...

本地部署 Llama-3-EvoVLM-JP-v2

本地部署 Llama-3-EvoVLM-JP-v2 0. 引言1. 关于 Llama-3-EvoVLM-JP-v22. 本地部署2-0. 克隆代码2-1. 安装依赖模块2-2. 创建 Web UI2-3.启动 Web UI2-4. 访问 Web UI 0. 引言 Sakana AI 提出了一种称为进化模型合并的方法&#xff0c;并使用该方法创建大规模语言模型&#xff…...

Evaluating the Generation Capabilities of Large Chinese Language Models

文章目录 题目摘要相关工作CG-Eval实验 题目 评估大型中文语言模型的生成能力 论文地址&#xff1a;https://arxiv.org/abs/2308.04823 项目地址&#xff1a;http://cgeval.besteasy.com/ 摘要 本文介绍了 CG-Eval&#xff0c;这是有史以来第一个全面的自动化评估框架&#xf…...

YOLOv8添加注意力模块并测试和训练

YOLOv8添加注意力模块并测试和训练 参考bilibili视频 yolov8代码库中写好了注意力模块&#xff0c;但是yolov8的yaml文件中并没用使用它&#xff0c;如下图的通道注意力和空间注意力以及两者的结合CBAM&#xff0c;打开conv.py文件可以看到&#xff0c;其中包含了各种卷积块的…...

「Unity3D」自动布局LayoutElement、ContentSizeFitter、AspectRatioFitter、GridLayoutGroup

布局元素与布局控制器 布局元素实现ILayoutElement接口&#xff0c;布局控制器实现ILayoutController接口&#xff0c;后者根据前者的属性控制具体布局——有些布局控制器也是布局元素&#xff0c;即同时实现这两个接口&#xff0c;如LayoutGroup。 public interface ILayout…...

【Golang 面试 - 进阶题】每日 3 题(十六)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…...

Redis2

为什么Redis要给缓存数据设置过期时间&#xff1f; 内存是有限的&#xff0c;如果缓存中的所有数据都是一直保存&#xff0c;很容易OOM Redis如何判断数据是否过期&#xff1f; 通过过期字典来保存数据的过期时间 过期删除策略 Redis采用的是定期删除惰性删除 Redis内存淘…...

C语言--函数

1. 函数定义 语法&#xff1a; 类型标识符 函数名&#xff08;形式参数&#xff09; {函数体代码 } &#xff08;1&#xff09;类型标识符 --- 数据类型&#xff08;函数要带出的结果的类型&#xff09; 注&#xff1a;数组类型不能做函数返回结果的类型&#xff0c;如果函…...

Shell 编程的高级技巧和实战应用

第一步&#xff1a;高级函数和模块化设计 1.1 高级函数设计 探讨函数的参数处理和默认值设置。示例&#xff1a;实现一个可以处理可选参数的函数。 #!/bin/bashgreet() {local name${1:-"World"} # 如果没有提供参数&#xff0c;使用默认值 "World"ech…...

VMWare虚拟机如何连接U盘

检查配置 1&#xff09;Win R键&#xff0c;输入services.msc&#xff0c;打开服务。 2&#xff09;将AMware USB Arbitration Services 服务开启&#xff0c;并设置为自动启动&#xff1b; 连接U盘 目前作者了解有两种连接方式&#xff0c;如有其他连接方式&#xff0c;欢…...

【学习笔记】后缀自动机(SAM)

前言 之前对后缀自动机的理解太浅薄了&#xff0c;故打算重新写一篇。 后缀自动机是什么 后缀自动机是一个字符串的所有后缀建起来的自动机。它把所有子串&#xff08;后缀的前缀&#xff09;用 O ( n ) O(n) O(n) 的空间装了起来。后缀自动机的边会构成一个 D A G DAG DA…...

Godot的节点与场景

要深入的理解节点与场景&#xff0c;我们需要跳出这两个概念来看他。说的再直白一些godot本质就是一个场景编辑器&#xff01; 场景的概念应该在我们平时看电影看电视时会经常提到&#xff0c;比如某一个打斗的场景&#xff0c;这个场景可能会被设在某一个街道&#xff0c;那么…...

C++ 学习(2) ---- std::cout 格式化输出

目录 std::cout 格式化输出简介使用成员函数使用流操作算子 std::cout 格式化输出简介 C 通常使用cout输出数据&#xff0c;和printf()函数相比&#xff0c;cout实现格式化输出数据的方式更加多样化&#xff1b; 一方面&#xff0c;cout 作为 ostream 类的对象&#xff0c;该类…...

网站建设问卷调研/正规seo排名多少钱

2019独角兽企业重金招聘Python工程师标准>>> 在Mysql中my.ini加上&#xff0c;lower_case_table_names 0&#xff0c;可以让表的导入导出识别大小写 转载于:https://my.oschina.net/idiot521/blog/277984...

做个网站需要多少钱./域名注册查询网站

教育 -计算机网络技术-章节资料考试资料-潍坊职业学院【】 随堂测验 1、【单选题】3.非屏蔽双绞线电缆用色标来区分不同的线对&#xff0c;计算机网络系统中常用的4对双绞线电缆有4中本色&#xff0c;他们分别是&#xff08; &#xff09;。 A、蓝色 橙色 绿色 紫色 B、蓝色 红…...

自己做的网站图片加载过慢/百度竞价排名的使用方法

Java方法05&#xff1a;可变参数 可变参数 JDK 1.5开始&#xff0c;Java支持传递同类型的可变参数给一个方法。 在方法声明中&#xff0c;在指定参数类型后面加一个省略号(…) 一个方法中中只能指定一个可变参数&#xff0c;它必须是方法的最后一个参数。任何普通的参数必须…...

wordpress wp_list_pages函数/如何做推广

小红书Android客户端第一个版本于2014年8月8日发布&#xff0c;转眼到了2016年8月8日&#xff0c;小红书Android版本发版两周年。趁机回顾一下小红书的Android版本&#xff0c;两年中我们踩过很多坑&#xff0c;收获很多经验&#xff0c;分享出来与大家共勉。 小红书从最初1.0…...

网站开发论文需要写什么/网址

本文实例讲述了java中request对象各种方法的使用。分享给大家供大家参考&#xff0c;具体如下&#xff1a;request对象是从客户端向服务器端发出请求&#xff0c;包括用户提交的信息以及客户端的一些信息。request对象是javax.servlet.http.HttpServletRequest类的实现实例。re…...

东莞做网站it s/郑州网站排名优化公司

文章目录整数规划的定义求解整数线性规划的方法分枝定界法——可求纯或混合整数线性规划。割平面法——可求纯或混合整数线性规划。隐枚举法——求解“0-1”整数规划匈牙利法——解决指派问题&#xff08;0-1规划特殊情形&#xff09;蒙特卡罗法——求解各种类型规划。使用matl…...