C++客户端Qt开发——多线程编程(二)
多线程编程(二)
③线程池
Qt中线程池的使用 | 爱编程的大丙
1>线程池
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务呢?
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
2>线程池原理
在各个编程语言的语种中都有线程池的概念,并且很多语言中直接提供了线程池,作为程序猿直接使用就可以了,下面介绍一下线程池的实现原理:
线程池的组成主要分为3个部分,这三部分配合工作就可以得到一个完整的线程池:
1. 任务队列,存储需要处理的任务,由工作的线程来处理这些任务
- 通过线程池提供的API函数,将一个待处理的任务添加到任务队列,或者从任务队列中删除
- 已处理的任务会被从任务队列中删除
- 线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程
2. 工作的线程(任务队列任务的消费者),N个
- 线程池中维护了一定数量的工作线程,他们的作用是是不停的读任务队列,从里边取出任务并处理
- 工作的线程相当于是任务队列的消费者角色,
- 如果任务队列为空,工作的线程将会被阻塞(使用条件变量/信号量阻塞)
- 如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作
3. 管理者线程(不处理任务队列中的任务),1个
- 它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测
- 当任务过多的时候可以适当的创建一些新的工作线程
- 当任务过少的时候可以适当的销毁一些工作的线程
3>线程池的使用QRunnable
在Qt中使用线程池需要先创建任务,添加到线程池中的每一个任务都需要是一个QRunnable
类型,因此在程序中需要创建子类继承QRunnable
这个类,然后重写run()
方法,在这个函数中编写要在线程池中执行的任务,并将这个子类对象传递给线程池,这样任务就可以被线程池中的某个工作的线程处理掉了。
QRunnable
类常用函数不多,主要是设置任务对象传给线程池后,是否需要自动析构。
| 在子类中必须要重写的函数, 里边是任务的处理流程 |
|
|
| 获取当前任务对象的析构方式,返回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函数如下:
| 获取和设置线程中的最大线程个数 |
| 给线程池添加任务, 任务是一个 QRunnable 类型的对象,如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理 |
| 如果线程池中没有空闲的线程了, 直接返回值, 任务添加失败, 任务不会添加到任务队列中 |
| 线程池中被激活的线程的个数(正在工作的线程个数) |
| 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了 |
| 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了 |
| 在每个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
特点:OMutexLocker
是QMutex
的辅助类,使用RAII
(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作。
用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
QMutex mutex;
{QMutexLocker locker(&mutex);//在作用域内自动上锁//访问共享资源//...
}//在作用域结束时自动解锁
QMutex mutex;
创建了一个QMutex
对象。- 在大括号
{}
内,创建了QMutexLocker
的实例locker
,它接受mutex
的地址作为参数。这将锁定mutex
。 - 在大括号内,可以安全地访问共享资源,因为互斥量已经被锁定,防止了其他线程同时访问。
- 当离开大括号的作用域时,
locker
的实例被销毁,它的析构函数会自动调用mutex.unlock()
,从而释放互斥量。
QReadWriteLocker
,QReadLocker
,QWriteLocker
特点:
QReadWriteLock
是读写锁类,用于控制读和写的并发访问。QReadLocker
用于读操作上锁,允许多个线程同时读取共享资源。QWriteLocker
用于写操作上锁,只允许一个线程写入共享资源。
用途:在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式。
OReadWriteLock rwLock;
//在读操作中使用读锁
{QReadLocker locker(&rwLock);//在作用域内自动上读锁//读取共享资源//……
}//在作用域结束时自动解读锁//在写操作中使用写锁
{QWriteLocker locker(&rwLock);//在作用域内自动上写锁//修改共享资源//……
}//在作用域结束时自动解写锁
2>条件变量
在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件满足才能执行,这时就会出现问题。这种情况下,线程会很自然地使用锁的机制来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进入了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另一个线程唤醒。
在Qt中,专门提供了QWaitCondition
类来解决像上述这样的问题。
特点:QWaitCondition
是Qt框架提供的条件变量类,用于线程之间的消息通信和同步。
用途:在某个条件满足时等待或唤醒线程,用于线程的同步和协调。
3>信号量
有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量。
特点:QSemaphore是Qt框架提供的计数信号量类,用于控制同时访问共享资源的线程数量。
用途:限制并发线程数量,用于解决一些资源有限的问题。
相关文章:

C++客户端Qt开发——多线程编程(二)
多线程编程(二) ③线程池 Qt中线程池的使用 | 爱编程的大丙 1>线程池 我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行…...

ubuntu20复现NBV探索
官网代码 后退地平线下一个最佳景观规划师 这个代码有些久远,issue里面有人已经在ubuntu20里面使用了3dmr,但是他那个代码我也运行不成功,docker网络一直也不佳,所以还是自己重新修改源码靠谱。 最终实现的代码等有时间上传到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中常用命令使用技巧 本次修炼方法请往下查看 🌈 欢迎莅临我的个人主页 👈这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合,智慧小天地! 🎇 免费获取相关内容文档关注&am…...

【Spring】Bean详细解析
1.Spring Bean的生命周期 整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。初始化这一步涉及到的步骤比较多,包含 Aware 接口的依赖注入、BeanPostProcessor 在初始化前后的处理以及 InitializingBean 和 init-method 的初始…...
决策树总结
决策树总结 决策树是一种广泛应用的机器学习算法,它模拟了人类进行决策时的逻辑思维过程,通过构建一棵树状结构来进行数据的分类或回归预测。决策树模型因其直观易懂、易于解释、能够处理多类问题以及无需进行复杂的特征缩放等优点,在数据挖…...

通俗易懂!495页看漫画学Python入门教程(全彩版)Git首发破万Star
前言 在编程的世界里,Python无疑是一颗璀璨的明星。从最初作为打发圣诞节闲暇时间的项目,到如今成为最受欢迎的程序设计语言之一,Python以其简洁、易学、强大的特点吸引了无数编程爱好者。然而,对于初学者来说,编程的…...
websocket实现简易聊天室
websocket实现简易聊天室 又做了一个关于websocket广播和在线人数统计的练习,实现一个简易的聊天室。 前端vue3 前端里的内容主要包含: 1.css的animation来实现公告从右到左的轮播。 2.websocket的onmessage里对不同消息的处理。 <template>&l…...

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

【机器学习算法基础】(基础机器学习课程)-10-逻辑回归-笔记
一、模型的保存与加载 逻辑回归是一种常见的机器学习算法,广泛用于分类问题。为了在不同的时间或环境下使用训练好的模型,我们通常需要将其保存和加载。 保存模型 训练模型:首先,你需要用你的数据训练一个逻辑回归模型。例如&…...
自动驾驶行业知识汇总
应届生月薪2W的自动驾驶开发、机器人、后端开发,软件开发该如何学习相关技术栈_哔哩哔哩_bilibili 两万字详解自动驾驶开发工具链的现状与趋势 (qq.com) 九章智驾 - 2023年度文章大合集 (qq.com) 九章 - 2022年度文章大合集 (qq.com)...
C#根据反射操作对象
前言 反射使用,让我们的程序可以动态增加一些功能,让原本固化的步骤逻辑变得动态,这是它的优点。当然使用反射首次加载会有性能损耗以及使用复杂;但是现在大家都在讲动态,使用好它应该是一个重要的编程理念提升。MVC、…...
打包python脚本(flask、jinja2)为exe文件
20240803 概述 在我很早时候学习python的时候,就利用过某个工具将其打包为exe文件,然后在没有python环境的机器上运行,这样可以减少安装python环境和各种库的过程。 最近在开发一个在虚拟机上运行的程序的时候就遇到了打包一些环境的问题&…...

嵌入式初学-C语言-练习三
#部分题目可能在之前的博客中有,请谅解,保证常见题型均被发出# 1.计算n以内所有正奇数的和 ? n值通过键盘输入 代码: 1 /*2 需求:计算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是一款以创作者为中心的智能音频插件系列。这些工具的特点是易于使用,搭配高级处理和优质音质。pure:bundle的所有插件都由sonible的智能插件系列中使用的技术驱动,但在设计时考虑到…...
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 是目前仍被广泛使用的前端框架之一,其特点包括响应式数据绑定、组件化开发等。 响应式系统:Vue2 使用 Object.defineProperty 来实现数据的响应式。每个组件实例在创建时,会将 dat…...

C语言 | Leetcode C语言题解之第316题去除重复字母
题目: 题解: 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 提出了一种称为进化模型合并的方法,并使用该方法创建大规模语言模型ÿ…...

Evaluating the Generation Capabilities of Large Chinese Language Models
文章目录 题目摘要相关工作CG-Eval实验 题目 评估大型中文语言模型的生成能力 论文地址:https://arxiv.org/abs/2308.04823 项目地址:http://cgeval.besteasy.com/ 摘要 本文介绍了 CG-Eval,这是有史以来第一个全面的自动化评估框架…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...

归并排序:分治思想的高效排序
目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法,由约翰冯诺伊曼在1945年提出。其核心思想包括: 分割(Divide):将待排序数组递归地分成两个子…...

基于小程序老人监护管理系统源码数据库文档
摘 要 近年来,随着我国人口老龄化问题日益严重,独居和居住养老机构的的老年人数量越来越多。而随着老年人数量的逐步增长,随之而来的是日益突出的老年人问题,尤其是老年人的健康问题,尤其是老年人产生健康问题后&…...

汇编语言学习(三)——DoxBox中debug的使用
目录 一、安装DoxBox,并下载汇编工具(MASM文件) 二、debug是什么 三、debug中的命令 一、安装DoxBox,并下载汇编工具(MASM文件) 链接: https://pan.baidu.com/s/1IbyJj-JIkl_oMOJmkKiaGQ?pw…...