yolov5目标检测多线程C++部署

C++多线程复习

下面的代码搭建了简单的一个生产者-消费者模型,在capture()函数中进行入队操作,infer()函数中进行出队操作,为了模拟采图-推理流程,在函数中调用Sleep()函数延时。

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <windows.h>std::queue<std::string> jobs;void capture()
{int id = 0;while (true){std::string name = std::to_string(id++) + ".jpg";std::cout << "capture: " << name << " jobs.size():" << jobs.size() << std::endl;jobs.push(name);Sleep(1000);}
}void infer()
{while (true){if (!jobs.empty()){auto pic = jobs.front();jobs.pop();std::cout <<"infer: "<< pic << std::endl;Sleep(1000);}}
}int main()
{std::thread t0(capture);std::thread t1(infer);t0.join();t1.join();return 0;
}

输出结果:

capture: 0.jpg jobs.size():0
infer: 0.jpg
capture: 1.jpg jobs.size():0
infer: 1.jpg
capture: 2.jpg jobs.size():0
infer: 2.jpg
capture: 3.jpg jobs.size():0
infer: 3.jpg
capture: 4.jpg jobs.size():0
infer: 4.jpg
capture: 5.jpg jobs.size():0
infer: 5.jpg
capture: 6.jpg jobs.size():0
infer: 6.jpg
capture: 7.jpg jobs.size():0
infer: 7.jpg
capture: 8.jpg jobs.size():0
infer: 8.jpg
capture: 9.jpg jobs.size():0
infer: 9.jpg
capture: 10.jpg jobs.size():0
infer: 10.jpg
...

现在我们把capture函数中的Sleep(1000)改成Sleep(500),再次执行程序,则输出:

capture: 0.jpg jobs.size():0
infer: 0.jpg
capture: 1.jpg jobs.size():0
infer: 1.jpg
capture: 2.jpg jobs.size():0
capture: 3.jpg jobs.size():1
infer: 2.jpg
capture: 4.jpg jobs.size():1
capture: 5.jpg jobs.size():2
infer: 3.jpg
capture: 6.jpg jobs.size():2
capture: 7.jpg jobs.size():3
infer: 4.jpg
capture: 8.jpg jobs.size():3
capture: 9.jpg jobs.size():4
infer: 5.jpg
capture: 10.jpg jobs.size():4
...

此时发现采图-推理流程不能同步。为了解决这个问题,加入对队列长度的限制:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <windows.h>std::queue<std::string> jobs;const int limit = 3;void capture()
{int id = 0;while (true){std::string name = std::to_string(id++) + ".jpg";std::cout << "capture: " << name << " jobs.size():" << jobs.size() << std::endl;if(jobs.size()< limit)jobs.push(name);Sleep(500);}
}void infer()
{while (true){if (!jobs.empty()){auto pic = jobs.front();jobs.pop();std::cout <<"infer: "<< pic << std::endl;Sleep(1000);}}
}int main()
{std::thread t0(capture);std::thread t1(infer);t0.join();t1.join();return 0;
}

此时输出结果:

capture: 0.jpg jobs.size():0
infer: 0.jpg
capture: 1.jpg jobs.size():0
infer: 1.jpg
capture: 2.jpg jobs.size():0
capture: 3.jpg jobs.size():1
infer: 2.jpg
capture: 4.jpg jobs.size():1
capture: 5.jpg jobs.size():2
infer: 3.jpg
capture: 6.jpg jobs.size():2
capture: 7.jpg jobs.size():3
infer: 4.jpg
capture: 8.jpg jobs.size():2
capture: 9.jpg jobs.size():3
infer: 5.jpg
capture: 10.jpg jobs.size():2
...

由于std::queue不是线程安全的数据结构,故引入锁std::mutex:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <windows.h>std::queue<std::string> jobs;std::mutex lock;void capture()
{int id = 0;while (true){{std::unique_lock<std::mutex> l(lock);std::string name = std::to_string(id++) + ".jpg";std::cout << "capture: " << name << " " << "jobs.size(): " << jobs.size() << std::endl;}Sleep(500);}
}void infer()
{ while (true){if (!jobs.empty()){{std::lock_guard<std::mutex> l(lock);auto job = jobs.front();jobs.pop();std::cout << "infer: " << job << std::endl;}Sleep(1000);}}
}int main()
{std::thread t0(capture);std::thread t1(infer);t0.join();t1.join();return 0;
}

此时输出:

capture: 0.jpg jobs.size(): 0
capture: 1.jpg jobs.size(): 0
capture: 2.jpg jobs.size(): 0
capture: 3.jpg jobs.size(): 0
capture: 4.jpg jobs.size(): 0
capture: 5.jpg jobs.size(): 0
capture: 6.jpg jobs.size(): 0
capture: 7.jpg jobs.size(): 0
capture: 8.jpg jobs.size(): 0
capture: 9.jpg jobs.size(): 0
capture: 10.jpg jobs.size(): 0
...

有时候生产者还需要拿到消费者处理之后的结果,因此引入std::promise和std::condition_variable对程序进行完善:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <windows.h>struct Job
{std::string input;std::shared_ptr<std::promise<std::string>> pro;
};std::queue<Job> jobs;std::mutex lock;std::condition_variable cv;const int limit = 5;void capture()
{int id = 0;while (true){Job job;{std::unique_lock<std::mutex> l(lock);std::string name = std::to_string(id++) + ".jpg";std::cout << "capture: " << name << " " << "jobs.size(): " << qjobs.size() << std::endl;cv.wait(l, [&]() { return qjobs.size() < limit; });job.input = name;job.pro.reset(new std::promise<std::string>());jobs.push(job);}auto result = job.pro->get_future().get();std::cout << result << std::endl;Sleep(500);}
}void infer()
{ while (true){if (!qjobs.empty()){{std::lock_guard<std::mutex> l(lock);auto job = jobs.front();jobs.pop();cv.notify_all();std::cout << "infer: " << job.input << std::endl;auto result = job.input + " after infer";job.pro->set_value(result);}Sleep(1000);}}
}int main()
{std::thread t0(capture);std::thread t1(infer);t0.join();t1.join();return 0;
}

输出:

capture: 0.jpg jobs.size(): 0
infer: 0.jpg
0.jpg after infer
capture: 1.jpg jobs.size(): 0
infer: 1.jpg
1.jpg after infer
capture: 2.jpg jobs.size(): 0
infer: 2.jpg
2.jpg after infer
capture: 3.jpg jobs.size(): 0
infer: 3.jpg
3.jpg after infer
capture: 4.jpg jobs.size(): 0
infer: 4.jpg
4.jpg after infer
capture: 5.jpg jobs.size(): 0
infer: 5.jpg
5.jpg after infer
capture: 6.jpg jobs.size(): 0
infer: 6.jpg
6.jpg after infer
capture: 7.jpg jobs.size(): 0
infer: 7.jpg
7.jpg after infer
capture: 8.jpg jobs.size(): 0
infer: 8.jpg
8.jpg after infer
capture: 9.jpg jobs.size(): 0
infer: 9.jpg
9.jpg after infer
capture: 10.jpg jobs.size(): 0
infer: 10.jpg
10.jpg after infer
...

yolov5目标检测多线程C++部署

有了上面的基础,我们来写一个基本的目标检测多线程部署程序,为了简单起见选用OpenCV的dnn作为推理框架,出于篇幅限制下面只给出main.cpp部分:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <windows.h>#include "yolov5.h"struct Job
{cv::Mat input_image;std::shared_ptr<std::promise<cv::Mat>> output_image;
};std::queue<Job> jobs;std::mutex lock;std::condition_variable c_v;const int limit = 10;void capture(cv::VideoCapture cap)
{while (true){Job job;cv::Mat frame;{cap.read(frame);if (frame.empty())break;std::unique_lock<std::mutex> l(lock);c_v.wait(l, [&]() { return jobs.size() < limit; });job.input_image = frame;job.output_image.reset(new std::promise<cv::Mat>());jobs.push(job);}cv::Mat result = job.output_image->get_future().get();cv::imshow("result", result);cv::waitKey(1);}
}void infer(cv::dnn::Net net)
{ while (true){if (!jobs.empty()){std::lock_guard<std::mutex> l(lock);auto job = jobs.front();jobs.pop();c_v.notify_all();cv::Mat input_image = job.input_image, blob, output_image;pre_process(input_image, blob);std::vector<cv::Mat> network_outputs;process(blob, net, network_outputs);post_process(input_image, output_image, network_outputs);job.output_image->set_value(output_image);}}
}int main(int argc, char* argv[])
{cv::VideoCapture cap("test.mp4");cv::dnn::Net net = cv::dnn::readNet("yolov5n.onnx");std::thread t0(capture, cap);std::thread t1(infer, net);t0.join();t1.join();return 0;
}

接下来我们模拟多个模型同时推理,先给出单线程串行的程序:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <windows.h>#include "yolov5.h"int main(int argc, char* argv[])
{cv::VideoCapture cap("test.mp4");//cap.open(0);cv::dnn::Net net1 = cv::dnn::readNet("yolov5n.onnx");cv::dnn::Net net2 = cv::dnn::readNet("yolov5s.onnx");cv::Mat frame;while (true) {clock_t start = clock();cap.read(frame);if (frame.empty())break;cv::Mat input_image = frame, blob;pre_process(input_image, blob);std::vector<cv::Mat> network_outputs1, network_outputs2;process(blob, net1, network_outputs1);process(blob, net2, network_outputs2);cv::Mat output_image1, output_image2;post_process(input_image, output_image1, network_outputs1);post_process(input_image, output_image2, network_outputs2);clock_t end = clock();std::cout << end - start << "ms" << std::endl;cv::imshow("result1", output_image1);cv::imshow("result2", output_image2);cv::waitKey(1);}return 0;
}

输出结果:

infer1+infer2:191ms
infer1+infer2:142ms
infer1+infer2:134ms
infer1+infer2:130ms
infer1+infer2:129ms
infer1+infer2:124ms
infer1+infer2:124ms
infer1+infer2:121ms
infer1+infer2:124ms
infer1+infer2:122ms
...

多线程并行的写法修改如下:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <windows.h>#include "yolov5.h"struct Job
{cv::Mat input_image;std::shared_ptr<std::promise<cv::Mat>> output_image;
};std::queue<Job> jobs1,jobs2;std::mutex lock1, lock2;std::condition_variable cv1, cv2;const int limit = 10;void capture(cv::VideoCapture cap)
{while (true){Job job1, job2;cv::Mat frame;clock_t start = clock();cap.read(frame);if (frame.empty())break;{std::unique_lock<std::mutex> l1(lock1);cv1.wait(l1, [&]() { return jobs1.size() < limit; });job1.input_image = frame;job1.output_image.reset(new std::promise<cv::Mat>());jobs1.push(job1);}{std::unique_lock<std::mutex> l2(lock2);cv1.wait(l2, [&]() { return jobs2.size() < limit; });job2.input_image = frame;job2.output_image.reset(new std::promise<cv::Mat>());jobs2.push(job2);}cv::Mat result1 = job1.output_image->get_future().get();cv::Mat result2 = job2.output_image->get_future().get();clock_t end = clock();std::cout <<"capture: "<< end - start << "ms" << std::endl;cv::imshow("result1", result1);cv::imshow("result2", result2);cv::waitKey(1);}
}void infer1(cv::dnn::Net net)
{ while (true){if (!jobs1.empty()){clock_t start = clock();std::lock_guard<std::mutex> l1(lock1);auto job = jobs1.front();jobs1.pop();cv1.notify_all();cv::Mat input_image = job.input_image, blob, output_image;pre_process(input_image, blob);std::vector<cv::Mat> network_outputs;process(blob, net, network_outputs);post_process(input_image, output_image, network_outputs);job.output_image->set_value(output_image);clock_t end = clock();std::cout << "infer1: " << end - start << "ms" << std::endl;}}
}void infer2(cv::dnn::Net net)
{while (true){if (!jobs2.empty()){clock_t start = clock();std::lock_guard<std::mutex> l2(lock2);auto job = jobs2.front();jobs2.pop();cv2.notify_all();cv::Mat input_image = job.input_image, blob, output_image;pre_process(input_image, blob);std::vector<cv::Mat> network_outputs;process(blob, net, network_outputs);post_process(input_image, output_image, network_outputs);job.output_image->set_value(output_image);clock_t end = clock();std::cout << "infer2: " << end - start << "ms" << std::endl;}}
}int main(int argc, char* argv[])
{cv::VideoCapture cap("test.mp4");//cap.open(0);cv::dnn::Net net1 = cv::dnn::readNet("yolov5n.onnx");cv::dnn::Net net2 = cv::dnn::readNet("yolov5s.onnx");std::thread t0(capture, cap);std::thread t1(infer1, net1);std::thread t2(infer2, net2);t0.join();t1.join();t2.join();return 0;
}

输出:

infer1: 98ms
infer2: 136mscapture: 155msinfer1: 80ms
infer2: 110ms
capture: 113ms
infer1: 92ms
infer2: 101mscapture: 103msinfer1: 85ms
infer2: 97ms
capture: 100ms
infer1: 85ms
infer2: 100mscapture: 102ms
...

上面的程序还有一点小问题:视频播放完时程序无法正常退出。继续修正如下:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <windows.h>#include "yolov5.h"struct Job
{cv::Mat input_image;std::shared_ptr<std::promise<cv::Mat>> output_image;
};std::queue<Job> jobs1,jobs2;std::mutex lock1, lock2;std::condition_variable cv1, cv2;const int limit = 10;bool stop = false;void capture(cv::VideoCapture cap)
{while (true){Job job1, job2;cv::Mat frame;clock_t start = clock();cap.read(frame);if (frame.empty()){stop = true;break;}{std::unique_lock<std::mutex> l1(lock1);cv1.wait(l1, [&]() { return jobs1.size()<limit; });job1.input_image = frame;job1.output_image.reset(new std::promise<cv::Mat>());jobs1.push(job1);}{std::unique_lock<std::mutex> l2(lock2);cv1.wait(l2, [&]() { return  jobs2.size() < limit; });job2.input_image = frame;job2.output_image.reset(new std::promise<cv::Mat>());jobs2.push(job2);}cv::Mat result1 = job1.output_image->get_future().get();cv::Mat result2 = job2.output_image->get_future().get();clock_t end = clock();std::cout <<"capture: "<< end - start << "ms" << std::endl;cv::imshow("result1", result1);cv::imshow("result2", result2);cv::waitKey(1);}
}void infer1(cv::dnn::Net net)
{ while (true){if (stop)break; //不加程序无法退出if (!jobs1.empty()){clock_t start = clock();std::lock_guard<std::mutex> l1(lock1);auto job = jobs1.front();jobs1.pop();cv1.notify_all();cv::Mat input_image = job.input_image, blob, output_image;pre_process(input_image, blob);std::vector<cv::Mat> network_outputs;process(blob, net, network_outputs);post_process(input_image, output_image, network_outputs);job.output_image->set_value(output_image);clock_t end = clock();std::cout << "infer1: " << end - start << "ms" << std::endl;}std::this_thread::yield(); //不加程序无法退出}
}void infer2(cv::dnn::Net net)
{while (true){if (stop)break; //不加程序无法退出if (!jobs2.empty()){clock_t start = clock();std::lock_guard<std::mutex> l2(lock2);auto job = jobs2.front();jobs2.pop();cv2.notify_all();cv::Mat input_image = job.input_image, blob, output_image;pre_process(input_image, blob);std::vector<cv::Mat> network_outputs;process(blob, net, network_outputs);post_process(input_image, output_image, network_outputs);job.output_image->set_value(output_image);clock_t end = clock();std::cout << "infer2: " << end - start << "ms" << std::endl;}std::this_thread::yield(); //不加程序无法退出}
}int main(int argc, char* argv[])
{cv::VideoCapture cap("test.mp4");cv::dnn::Net net1 = cv::dnn::readNet("yolov5n.onnx");cv::dnn::Net net2 = cv::dnn::readNet("yolov5s.onnx");std::thread t0(capture, cap);std::thread t1(infer1, net1);std::thread t2(infer2, net2);t0.join();t1.join();t2.join();return 0;
}

多个视频不同模型同时推理:

#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <windows.h>#include "yolov5.h"bool stop = false;void print_time(std::string video)
{auto now = std::chrono::system_clock::now();uint64_t dis_millseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()- std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count() * 1000;time_t tt = std::chrono::system_clock::to_time_t(now);auto time_tm = localtime(&tt);char time[100] = { 0 };sprintf(time, "%d-%02d-%02d %02d:%02d:%02d %03d", time_tm->tm_year + 1900,time_tm->tm_mon + 1, time_tm->tm_mday, time_tm->tm_hour,time_tm->tm_min, time_tm->tm_sec, (int)dis_millseconds);std::cout << "infer " << video << " 当前时间为:" << time << std::endl;
}void capture(std::string video, cv::dnn::Net net)
{cv::VideoCapture cap(video);while (cv::waitKey(1) < 0){cv::Mat frame;cap.read(frame);if (frame.empty()){stop = true;break;}cv::Mat input_image = frame, blob, output_image;pre_process(input_image, blob);std::vector<cv::Mat> network_outputs;process(blob, net, network_outputs);post_process(input_image, output_image, network_outputs);print_time(video);cv::imshow(video, output_image);}
}int main(int argc, char* argv[])
{std::string video1("test1.mp4");std::string video2("test2.mp4");cv::dnn::Net net1 = cv::dnn::readNet("yolov5n.onnx");cv::dnn::Net net2 = cv::dnn::readNet("yolov5s.onnx");std::thread t1(capture, video1, net1);std::thread t2(capture, video2, net2);t1.join();t2.join();return 0;
}

推理效果如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/29747.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rabbitmq的消息确认

配置文件 spring:rabbitmq:publisher-confirm-type: correlated #开启确认回调publisher-returns: true #开启返回回调listener:simple:acknowledge-mode: manual #设置手动接受消息消息从生产者到交换机 无论消息是否到交换机ConfirmCallback都会触发。 Resourceprivate Rabb…

adb用法,安卓的用户CA证书放到系统CA证书下

设备需root&#xff01;&#xff01;设备需root&#xff01;&#xff01;设备需root&#xff01;&#xff01; ​​​​​​​测试环境&#xff1a;redmi 5 plus、miui10 9.9.2dev&#xff08;安卓8.1&#xff09;、已root win下安装手机USB驱动&#xff08;过程略&#xff0c…

【Spring专题】Spring之底层架构核心概念解析

目录 前言前置知识课程内容一、BeanDefinition&#xff1a;图纸二、BeanDefinitionReader&#xff1a;图纸读取器——Spring工厂基础设施之一2.1 AnnotatedBeanDefinitionReader2.2 XmlBeanDefinitionReader2.3 ClassPathBeanDefinitionScanner 三、BeanFactory&#xff1a;生产…

关于在c++中使用数组名作为函数参数,或者使用数组名的地址作为函数参数问题的一些研究

前言 使用数组名作为函数参数&#xff0c;或者使用数组名的地址作为函数参数&#xff0c;常常出现于对于字符串的读入问题之中。 常有以下两种写法&#xff1a; 这是使用数组名作为函数参数 #include<cstdio> char s[100]; int main() {scanf("%s",s); }在…

Jmeter-获取接口响应头(Response headers)信息进行关联

文章目录 Jmeter-获取接口响应头&#xff08;Response headers&#xff09;信息进行关联使用正则表达式提取器将Set-Cookie的值提取出来在其余接口中关联该提取信息运行查看关联是否成功 Jmeter-获取接口响应头&#xff08;Response headers&#xff09;信息进行关联 获取某一…

重发布选路最佳实验

题目 IP地址配置 R1&#xff1a; R2&#xff1a; R3&#xff1a; R4&#xff1a; 双点重发布 R2&#xff1a; rip 1 version 2 network 12.0.0.0 network 2.0.0.0 import-route ospf 1 ospf 1 import-route rip 1 route-policy R2 area 0.0.0.0 network 23.1.1.0 0.0…

ElasticSearch:环境搭建步骤

1、拉取镜像 docker pull elasticsearch:7.4.0 2、创建容器 docker run -id --name elasticsearch -d --restartalways -p 9200:9200 -p 9300:9300 -v /usr/share/elasticsearch/plugins:/usr/share/elasticsearch/plugins -e "discovery.typesingle-node" elasti…

腾讯云COS的快速接入

背景 最近在研究一个剪贴板粘贴工具&#xff0c;实现粘贴图片&#xff0c;返回可访问的地址&#xff0c;这个在我的哔哩哔哩上有出一期视频&#x1f92d;。但是&#xff0c;我发现部分博客平台不能正常的转载我的图片链接&#xff0c;于是研究了一下腾讯云的COS&#xff08;阿…

linux程序保护机制gcc编译选项

预备知识&#xff1a; 计算机内存的结构通常包括以下几个主要部分&#xff1a; 1.代码段(Code Segment)&#xff1a;也称为文本段&#xff0c;存储程序的可执行指令。代码段是被标记为可执行的&#xff0c;程序从代码段中获取指令并执行。 2.数据段(Data Segment)&#xff1a…

现代C++中的从头开始深度学习:【4/8】梯度下降

一、说明 在本系列中&#xff0c;我们将学习如何仅使用普通和现代C编写必须知道的深度学习算法&#xff0c;例如卷积、反向传播、激活函数、优化器、深度神经网络等。 在这个故事中&#xff0c;我们将通过引入梯度下降算法来介绍数据中 2D 卷积核的拟合。我们将使用卷积和上一个…

大数据技术之Hadoop:HDFS集群安装篇(三)

目录 分布式文件系统HDFS安装篇 一、为什么海量数据需要分布式存储 二、 分布式的基础架构分析 三、 HDFS的基础架构 四 HDFS集群环境部署 4.1 下载安装包 4.2 集群规划 4.3 上传解压 4.4 配置HDFS集群 4.5 准备数据目录 4.6 分发hadoop到其他服务器 4.7 配置环境变…

OpenStack监控工具

OpenStack是一个开源的云计算管理平台项目&#xff0c;是一系列软件开源项目的组合。由NASA和Rackspace合作研发并发起&#xff0c;以Apache许可证&#xff08;Apache软件基金会发布的一个自由软件许可证&#xff09;授权。 OpenStack为私有云和公有云提供可扩展的弹性的云计算…

Cadvisor+InfluxDB+Grafan+Prometheus(详解)

目录 一、CadvisorInfluxDBGrafan案例概述 &#xff08;一&#xff09;Cadvisor Cadvisor 产品特点&#xff1a; &#xff08;二&#xff09;InfluxDB InfluxDB应用场景&#xff1a; InfluxDB主要功能&#xff1a; InfluxDB主要特点&#xff1a; &#xff08;三&#…

·[K8S:使用calico网络插件]:解决集群节点NotReady问题

文章目录 一&#xff1a;安装calico&#xff1a;1.1&#xff1a;weget安装Colico网络通信插件&#xff1a;1.2&#xff1a;修改calico.yaml网卡相关配置&#xff1a;1.2.1&#xff1a;查看本机ip 网卡相关信息&#xff1a;1.2.2&#xff1a;修改calico.yaml网卡interface相关信…

深度解读|一站式ABI平台 Smartbi Insight V11 能力再升级

纵观过去&#xff0c;我们发现汽车和BI的发展有异曲同工之妙。 100来年&#xff0c;汽车的动力从蒸汽到燃油再到新能源&#xff0c;汽车的操控方式从手动到自动再到智能无人驾驶。而在BI领域&#xff0c;自1958年BI的概念提出后&#xff0c;底层数据准备从报表开发、Cube多维模…

【CI/CD】Git Flow 分支模型

Git Flow 分支模型 1.前言 Git Flow 模型&#xff08;本文所阐述的分支模型&#xff09;构思于 2010 年&#xff0c;也就是 Git 诞生后不久&#xff0c;距今已有 10 多年。在这 10 多年中&#xff0c;Git Flow 在许多软件团队中大受欢迎。 在这 10 多年里&#xff0c;Git 本身…

最大子数组和【力扣53】

一、解题思路 Max[i]表示&#xff1a;以nums[i]为开头的所有连续子数组和的最大值。 由此可以推出Max[i-1]和Max[i]的关系&#xff1a; 若Max[i]>0&#xff1a;Max[i-1]nums[i-1]Max[i]&#xff1b; 否则&#xff1a;Max[i-1]nums[i-1]&#xff1b; 则ansMAX&#xff0…

RISC-V走向开放服务器规范

原文&#xff1a;RISC-V Moving Toward Open Server Specification 作者&#xff1a;Agam Shah 转载自&#xff1a;https://www.hpcwire.com/2023/07/24/risc-v-moving-toward-open-server-specification/ 中文翻译&#xff1a; 2023年7月24日 RISC-V International目前正…

亚马逊 EC2服务器下部署java环境

1. jdk 1.8 安装 1.1 下载jdk包 官网 Java Downloads | Oracle tar.gz 包 下载下来 1.2 本地连接 服务器 我用的是亚马逊的ec2 系统是 ubuntu 的 ssh工具是 Mobaxterm , 公有dns 创建实例时的秘钥 链接 Mobaxterm 因为使用的 ubuntu 所以登录的 名称 就是 ubuntu 然后 …

【Ubuntu】简化反向代理和个性化标签页体验

本文将介绍如何使用Docker部署Nginx Proxy Manager和OneNav&#xff0c;两个功能强大且易用的工具。Nginx Proxy Manager用于简化和管理Nginx反向代理服务器的配置&#xff0c;而OneNav则提供个性化的新标签页体验和导航功能。通过本文的指导&#xff0c;您将学习如何安装和配置…