YOLOv8 + openVINO 多线程数据读写顺序处理

news2024/11/20 8:42:27

多线程数据读写顺序处理

在这里插入图片描述

一个典型的生产者-消费者模型,在这个模型中,多个工作线程并行处理从共享队列中获取的数据,并将处理结果以保持原始顺序的方式放入另一个队列。

多线程处理模型,具体细节如下:

1.数据:数据里必须有个递增的标识符和一个结束标识(ending)
2. 读队列(安全队列):用于存放待处理的数据。

  1. 处理线程:每个线程都是一个死循环读数据-处理数据-写数据,它们被编号为1、2、3、4等。这些线程负责从读队列中取出数据进行处理。线程的结束:判断数据里的ending为true.

  2. 结果聚合:处理完成后,判断数据的递增的标识符,是否为全局的递增的标识符,如果相等 继续执行。以保持数据的一致性。

  3. 写队列(安全队列):用于处理好的数据按照读的顺序写入,写入数据到输出队列的顺序是保持一致的。

自定义设计多线程模版:

#include "queuestable.h"

#ifndef QUEUESTABLE_H
#define QUEUESTABLE_H

#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <atomic>


//定义一个线程安全的队列

template <typename T>
class QueueStable
{
public:

    QueueStable() = default;
    QueueStable(const QueueStable<T>&) = delete;
    QueueStable& operator=(const QueueStable<T>&) = delete;

    QueueStable(unsigned int max_size)
    {
        m_max_size = max_size;
    }

    //设置存放数据的最大容量
    void set_max_size(unsigned int max_size)
    {
        m_max_size = max_size;
    }

    void push(T value)
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        //队列大于20就阻塞
        m_cv.wait(lock, [&] { return m_queue.size() < m_max_size; }); //为 true 继续执行,否则,解锁-》等待。唤醒后,加锁
        //入队
        m_queue.push(std::move(value));
        //m_queue.push(value);
        //解锁
        lock.unlock();
        m_cv.notify_one(); //唤醒另一个
    }
    T pop()
    {
        //加锁
        std::unique_lock<std::mutex> lock(m_mutex);
        m_cv.wait(lock, [&] { return !m_queue.empty(); }); //为 true 继续执行,否则,解锁-》等待。唤醒后,加锁
        //出队
        T data = std::move(m_queue.front());
        //T data = m_queue.front();
        m_queue.pop();
        //解锁
        lock.unlock();
        m_cv.notify_one(); //唤醒另一个

        return data;
    }

    T front() const
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_queue.front();
    }

    // bool empty() const
    // {
    //     std::lock_guard<std::mutex> lock(m_mutex);
    //     return m_queue.empty();
    // }
    // int size()
    // {
    //     //加锁
    //     std::lock_guard<std::mutex> lock(m_mutex);
    //     return m_queue.size();
    // }


private:
    std::queue<T> m_queue;

    std::mutex m_mutex;
    std::condition_variable m_cv;

    unsigned int m_max_size = 1;
};


//包数据
struct BaseData
{
    //`递增的标识符`  sequence_number
    int64_t  sequence_number ; //记录当前位置,保证数据顺序一致
    bool ending = false; //true 代表结尾

};

//多线程处理
template <typename T,typename P>
class MultiThreadProcessing
{

    static_assert(std::is_base_of<BaseData, T>::value, "T must be derived from BaseData!");

public:

    enum class ThreadMode
    {
        Detach,  // 分离
        Join     // 阻塞
    };

    MultiThreadProcessing()
    {
        m_ending = false;
        m_thread_mode = ThreadMode::Join;
    }

    //设置上下文结构使用的数据 - 必须设置
    void set_contexts(const std::vector<std::shared_ptr<P>>& contexts)
    {
        //生成用于线程的结构
        for(int i=0; i<contexts.size(); ++i)
        {
            auto& context = m_contexts.emplace_back();
            context.id = i;
            context.ext_context = contexts[i];

        }
    }

    //设置运行的处理函数 - 必须设置
    void set_execute_function(const std::function<void(const std::shared_ptr<T>&,const std::shared_ptr<P>&)>& function)
    {
        m_execute_function = function;
    }

    //设置线程模式
    void set_thread_mode(MultiThreadProcessing::ThreadMode thread_mode = ThreadMode::Join)
    {
        m_thread_mode = thread_mode;
    }




    //多线程同步检测
    void start(const std::shared_ptr<QueueStable<std::shared_ptr<T>>>& read_queue,const std::shared_ptr<QueueStable<std::shared_ptr<T>>>& write_queue)
    {
        //检查处理函数是否可调用
        if (!m_execute_function)
        {
            // 打印异常信息
            std::cerr << "MultiThreadProcessing: Invalid to execute function." << std::endl;
            throw std::runtime_error("Invalid to execute function.");
            return;
        }

        std::shared_ptr<DataPacket> data_packer = read_queue->pop();//取出第一个包,查看序号 不处理
        m_sequence_number = data_packer->sequence_number;//先找到第一个包的序号
        m_ckeck_sequence_number = data_packer->sequence_number;
        write_queue->push(data_packer);
        m_sequence_number++;
        m_ckeck_sequence_number++;

        //循环开启线程
        for(auto& context : m_contexts)
        {
            //开启线程
            context.thread = std::make_shared<std::thread>([this,&context,&read_queue,&write_queue]()
            {

                std::cout << "context.id: " << context.id<<" Started." << std::endl;

                for(;;)
                {
                    // 读数据 逻辑
                    //加锁
                    m_read_mutex.lock();

                    //为true,末尾标志,不在继续,直接结束
                    if(m_ending)
                    {
                        m_read_mutex.unlock();
                        break;
                    }
                    context.temp_data = read_queue->pop();
                    //检测是否为递增1
                    if(m_ckeck_sequence_number.load() == context.temp_data->sequence_number)
                    {
                        m_ckeck_sequence_number++;
                    }
                    else
                    {
                        m_ending = true;
                        // 打印异常信息
                        std::cerr << "MultiThreadProcessing: The sequence number must be incremented by one." << std::endl;
                        throw std::runtime_error("The sequence number must be incremented by one.");
                        break;
                        //异常
                    }

                    //true 包的末尾,結束
                    if(context.temp_data->ending)
                    {
                        m_ending = true;
                        std::unique_lock<std::mutex> lock(m_write_mutex);
                        m_write_cond_var.wait(lock, [&] { return m_sequence_number.load() == context.temp_data->sequence_number; }); //为 true 继续执行,否则,解锁-》等待。唤醒后,加锁
                        //写入包
                        write_queue->push(context.temp_data);
                        //解锁
                        lock.unlock();
                        m_write_cond_var.notify_all(); //唤醒所有
                        break;
                    }
                    m_read_mutex.unlock();


                    //自定义函数处理数据
                    m_execute_function(context.temp_data,context.ext_context);

                    //写数据逻辑
                    std::unique_lock<std::mutex> lock(m_write_mutex);
                    m_write_cond_var.wait(lock, [&] { return m_sequence_number.load() == context.temp_data->sequence_number; }); //为 true 继续执行,否则,解锁-》等待。唤醒后,加锁
                    //写入包
                    write_queue->push(context.temp_data);
                    m_sequence_number++;
                    //解锁
                    lock.unlock();
                    m_write_cond_var.notify_all(); //唤醒所有

                }
                std::cout << "context.id: " << context.id<<" Finished." << std::endl;

            });
        }

        //阻塞线程
        for(auto& context : m_contexts)
        {
            if (context.thread->joinable())
            {
                if(m_thread_mode == ThreadMode::Join)
                {
                    context.thread->join();
                }
                else if (m_thread_mode == ThreadMode::Detach)
                {
                    context.thread->detach();
                }
            }
        }
    }

private:

    struct S
    {
        std::shared_ptr<T> temp_data;
        std::shared_ptr<std::thread> thread;
        uint32_t id;

        std::shared_ptr<P> ext_context; //外部的自定义数据
    };
    std::vector<S> m_contexts;//每个线程的临时数据,用于多线程临时

    //结束标志
    std::atomic<bool> m_ending = false;

    std::mutex m_read_mutex;//读锁
    std::mutex m_write_mutex;//写锁
    std::condition_variable m_write_cond_var;//写条件变量  同步数据顺序

    std::atomic<int64_t> m_sequence_number;//保证数据顺序
    std::atomic<int64_t> m_ckeck_sequence_number;//检查数据序号,若数据序号不是递增为1,抛出异常

    std::function<void(const std::shared_ptr<T>&,const std::shared_ptr<P>&)> m_execute_function; //每个数据单次处理函数
    MultiThreadProcessing::ThreadMode m_thread_mode = ThreadMode::Join;
};

#endif // QUEUESTABLE_H


QueueStable 安全队列类
BaseData 基本数据
MultiThreadProcessing多线程模版类 ,处理继承基本数据BaseData的结构体

使用 QueueStable类确保数据的读写正常,MultiThreadProcessing多线程处理数据,保证数据先读的先写。

举例:下面实现openVINO + yolov8推理代码,使用上面的多线程模版:

yolov8_2.h

#ifndef YOLOV8_2_H
#define YOLOV8_2_H

#include "filterbase.h"
#include "queuestable.h"
//包数据
struct DataPacket :  BaseData
{
    AVMediaType av_media_type = AVMEDIA_TYPE_UNKNOWN;//记录当前的索引,分辨是音频还是视频,字幕,等
    
    std::shared_ptr<AVPacketPtr> packet; //存放包
    
    //记录av_media_type类型数据,如果数据没解码,数据在packet中
    std::vector<std::shared_ptr<AVFramePtr>> frame_vector;
};

class YOLOV8_2 : public FilterBase
{
public:

    struct Config
    {
        float nms_threshold;
        float score_threshold;
        std::string model_path;
        std::string bin_path = {};
        std::string properties = "GPU.0";
        uint32_t image_interval = 2; // 处理图像的间隔,每 image_interval处理一次
    };

    struct Detection
    {
        int class_id;
        std::string class_name; //类型名
        float confidence;//置信度
        cv::Rect box; // 矩形框位置
    };

    struct InferContext
    {
        ov::InferRequest request;
        uint32_t image_interval; // 处理图像的间隔,每 image_interval处理一次
    };


    YOLOV8_2(){}
    YOLOV8_2(const Config& config);
    ~YOLOV8_2(){}

    //实现基类的纯虚函数 start 是个接口,用于实现多态,基类不做实现
    void start(const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& read_queue,
               const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& write_queue) override;
    //多线程同步检测
    void detect(const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& read_queue,
                       const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& write_queue);

protected:
    void initial();

    //预处理
    ov::Tensor preprocess(const cv::Mat& frame, cv::Mat& pre_frame);
    //后处理
    void postprocess(cv::Mat& frame, const ov::Tensor& output_tensor);
    cv::Mat letterbox(const cv::Mat& input_image, const cv::Size& target_size, const cv::Scalar& fill_color = cv::Scalar(0, 0, 0), float* m_ratio = nullptr,int* m_top_offset = nullptr,int* m_left_offset = nullptr);


private:
    Config m_config; // 参数
    float m_ratio; // 原图与模型输入图 缩放比例
    int m_top_offset;
    int m_left_offset;

    ov::CompiledModel m_compiled_model;
    MultiThreadProcessing<DataPacket,InferContext> m_multi_thread;

    std::vector<std::shared_ptr<InferContext>> m_infer_request_vector; //推理列表,用于多路推理
    uint32_t m_infer_request_size;
};
#endif // YOLOV8_2_H
#include "yolov8_2.h"
const std::vector<std::string> coconame = { "person",
                                           "bicycle",
                                           "car",
                                           "motorcycle",
                                           "airplane",
                                           "bus",
                                           "train",
                                           "truck",
                                           "boat",
                                           "traffic light",
                                           "fire hydrant",
                                           "stop sign",
                                           "parking meter",
                                           "bench",
                                           "bird",
                                           "cat",
                                           "dog",
                                           "horse",
                                           "sheep",
                                           "cow",
                                           "elephant",
                                           "bear",
                                           "zebra",
                                           "giraffe",
                                           "backpack",
                                           "umbrella",
                                           "handbag",
                                           "tie",
                                           "suitcase",
                                           "frisbee",
                                           "skis",
                                           "snowboard",
                                           "sports ball",
                                           "kite",
                                           "baseball bat",
                                           "baseball glove",
                                           "skateboard",
                                           "surfboard",
                                           "tennis racket",
                                           "bottle",
                                           "wine glass",
                                           "cup",
                                           "fork",
                                           "knife",
                                           "spoon",
                                           "bowl",
                                           "banana",
                                           "apple",
                                           "sandwich",
                                           "orange",
                                           "broccoli",
                                           "carrot",
                                           "hot dog",
                                           "pizza",
                                           "donut",
                                           "cake",
                                           "chair",
                                           "couch",
                                           "potted plant",
                                           "bed",
                                           "dining table",
                                           "toilet",
                                           "tv",
                                           "laptop",
                                           "mouse",
                                           "remote",
                                           "keyboard",
                                           "cell phone",
                                           "microwave",
                                           "oven",
                                           "toaster",
                                           "sink",
                                           "refrigerator",
                                           "book",
                                           "clock",
                                           "vase",
                                           "scissors",
                                           "teddy bear",
                                           "hair drier",
                                           "toothbrush" };

YOLOV8_2::YOLOV8_2(const YOLOV8_2::Config& config)
{
    this->m_infer_request_size = 12;
    m_config = config;
    initial();
}

//多线程同步检测
void YOLOV8_2::detect(const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& read_queue,
                           const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& write_queue)
{

    std::function<void(const std::shared_ptr<DataPacket>&,const std::shared_ptr<InferContext>&)> lambda = [this](const std::shared_ptr<DataPacket>& data_packer,const std::shared_ptr<InferContext>& context)
    {
        if(data_packer->av_media_type == AVMEDIA_TYPE_VIDEO && !data_packer->frame_vector.empty())
        {
            for(const auto& frame : data_packer->frame_vector)
            {
                if(frame->get_number() % context->image_interval != 0)
                {
                    continue;
                }

                // 创建 cv::Mat 对象,注意这里直接使用 AVFrame 的数据
                cv::Mat image = cv::Mat(frame->get()->height, frame->get()->width, CV_8UC3, frame->get()->data[0], frame->get()->linesize[0]);

                if (!image.empty())
                {
                    cv::Mat pre_image;//临时存储
                    //预处理
                    ov::Tensor input_tensor = preprocess(image,pre_image);
                    //开始推理
                    context->request.set_input_tensor(input_tensor);
                    context->request.infer();
                    //等待完成处理结果
                    const ov::Tensor& output_tensor = context->request.get_output_tensor();
                    this->postprocess(image, output_tensor);
                }
            }
        }
    };
    m_multi_thread.set_contexts(m_infer_request_vector);
    m_multi_thread.set_execute_function(lambda);
    m_multi_thread.start(read_queue,write_queue);
}
void YOLOV8_2::start(const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& read_queue,const std::shared_ptr<QueueStable<std::shared_ptr<DataPacket>>>& write_queue)
{
    detect(read_queue,write_queue);
}
void YOLOV8_2::initial()
{
    //创建推理引擎 ie
    ov::Core core;
    //读取模型

    /*
        * std::shared_ptr<ov::Model> model = core.read_model(this->onnx_path);
        * @brief从IR / ONNX / PDPD / TF / TFLite文件格式读取模型。
        * @param model_path模型的路径。
        * @param bin_path数据文件的路径。
        * 对于IR格式(*.bin) :
        * 如果`bin_path`为空,将尝试读取与XML同名的bin文件
        * *如果没有找到同名的bin文件,将加载无权重的IR。
        * 对于以下文件格式,不使用`bin_path`参数:
        * ONNX格式(*.onnx)
        * *PDPD(*.pdmodel)
        * *TF(*.pb)
        * *TFLite(*.tflite)
        * @返回一个模型。
        */

    std::shared_ptr<ov::Model> model = core.read_model(m_config.model_path,m_config.bin_path);
    ov::preprocess::PrePostProcessor ppp = ov::preprocess::PrePostProcessor(model);

    ppp.input().tensor().set_element_type(ov::element::u8).set_layout("NHWC").set_color_format(ov::preprocess::ColorFormat::BGR)/*.set_spatial_static_shape(640, 640) //640*640 yolov8输入大小*/;
    //ppp.input().tensor().set_shape(ov::PartialShape({ 1,640,640,3 }));//自定义输入大小,确保和模型大小同样
    ppp.input().preprocess().convert_layout("NCHW").convert_element_type(ov::element::f32).convert_color(ov::preprocess::ColorFormat::RGB).scale({ 255, 255, 255 });// .scale({ 112, 112, 112 });
    //ppp.input().preprocess().resize(ov::preprocess::ResizeAlgorithm::RESIZE_NEAREST, 640, 640);

    //ppp.input().model().set_layout("NCHW");
    ppp.output().postprocess().convert_element_type(ov::element::f32);
    //ppp.output().tensor().set_element_type(ov::element::f32);
    model = ppp.build();
    this->m_compiled_model = core.compile_model(model,m_config.properties);

    //创建推理请求
    for(size_t i=0;i<m_infer_request_size;++i)
    {
        std::shared_ptr<InferContext> sh = std::make_shared<InferContext>();
        sh->request = m_compiled_model.create_infer_request();
        sh->image_interval = m_config.image_interval;
        m_infer_request_vector.push_back(sh);
    }
}

// Letterbox 缩放函数
/*
 * input_image 输入原图像
 * target_size 目标图像大小
 * fill_color 填充颜色
 * m_ratio 缩放比例
 * m_top_offset 缩放的图像 在目标图像中的 y 位置
 * m_left_offset 缩放的图像 在目标图像中的 x 位置
 */
cv::Mat YOLOV8_2::letterbox(const cv::Mat& input_image, const cv::Size& target_size, const cv::Scalar& fill_color, float* m_ratio,int* m_top_offset,int* m_left_offset)
{
    //输出图像
    cv::Mat output_image(target_size, input_image.type(), fill_color);

    //输入图像和输出图像 高度和宽度 都相等,直接复制返回
    if(input_image.cols == output_image.cols && input_image.rows == output_image.rows)
    {
        input_image.copyTo(output_image);
        //获取比例
        if (m_ratio)
        {
            *m_ratio = 1.0;
        }

        if (m_top_offset)
        {
            *m_top_offset = 0;
        }

        if (m_left_offset)
        {
            *m_left_offset = 0;
        }
        return output_image;

    }
    float r = 0.0;
    cv::Rect dest_rect;
    //输入图像宽 > 图像高,宽对齐,高至中
    if (input_image.cols > input_image.rows)
    {
        // 宽缩放 m_ratio ,那么高也要缩放 m_ratio
        r = static_cast<float>(input_image.cols) / output_image.cols;
        int new_rows = static_cast<int>(input_image.rows / r);
        dest_rect = cv::Rect(0, (output_image.rows - new_rows) / 2, output_image.cols, new_rows);

        //dest_rect = cv::Rect(0, 0, output_image.cols, new_rows);
    }
    else
    {
        // 高缩放 m_ratio ,那么宽也要缩放 m_ratio
        r = static_cast<float>(input_image.rows) / output_image.rows;
        int new_cols = static_cast<int>(input_image.cols / r);
        dest_rect = cv::Rect((output_image.cols - new_cols) / 2, 0, new_cols, output_image.rows);
    }
    //获取比例
    if (m_ratio)
    {
        *m_ratio = r;
    }
    if (m_top_offset)
    {
        *m_top_offset = dest_rect.y;
    }

    if (m_left_offset)
    {
        *m_left_offset = dest_rect.x;
    }
    cv::resize(input_image, output_image(dest_rect), dest_rect.size(), cv::INTER_LINEAR);
    return output_image;
}
//预处理
ov::Tensor YOLOV8_2::preprocess(const cv::Mat& frame, cv::Mat& pre_frame)
{
    //预处理
    const ov::Shape& shape = m_compiled_model.input().get_shape();
    //shape 对应 ppp.input().tensor().set_element_type(ov::element::u8).set_layout("NHWC")  中 NHWC
    pre_frame = letterbox(frame, cv::Size(shape.at(2), shape.at(1)), cv::Scalar(100, 100, 100), &m_ratio, &m_top_offset, &m_left_offset);
    uchar* input_data = pre_frame.data;

    return ov::Tensor(m_compiled_model.input().get_element_type(), m_compiled_model.input().get_shape(), input_data);
}
//后处理
void YOLOV8_2::postprocess(cv::Mat& frame, const ov::Tensor& output_tensor)
{
    std::vector<cv::Rect> boxes;
    std::vector<int> class_ids;
    std::vector<float> confidences;


    const ov::Shape& output_shape = output_tensor.get_shape();
    const int& out_rows = output_shape.at(1);
    const int& out_cols = output_shape.at(2);

    const cv::Mat det_output(out_rows, out_cols, CV_32F, (float*)output_tensor.data<float>());

    CHECK(det_output.cols == 8400)
    CHECK(det_output.rows == 84)

    //找到所有符合的 类别 矩形,置信度,
    for (int i = 0; i < det_output.cols; ++i)
    {
        const cv::Mat& classes_scores = det_output.col(i).rowRange(4, 84);
        cv::Point class_id_point;
        double score;
        cv::minMaxLoc(classes_scores, nullptr, &score, nullptr, &class_id_point);

        //阈值大于0.25 认为检测出结果
        //if (score > 0.3)
        {
            //坐标
            const float& x = det_output.at<float>(0, i);
            const float& y = det_output.at<float>(1, i);
            const float& w = det_output.at<float>(2, i);
            const float& h = det_output.at<float>(3, i);
            cv::Rect box;
            box.x = static_cast<int>(x);
            box.y = static_cast<int>(y);
            box.width = static_cast<int>(w);
            box.height = static_cast<int>(h);

            boxes.push_back(box);
            class_ids.push_back(class_id_point.y);
            confidences.push_back(score);
        }
    }

    std::vector<int> nms_result;
    //nms 去重,找到最优数据
    cv::dnn::NMSBoxes(boxes, confidences, m_config.score_threshold, m_config.nms_threshold, nms_result);

    std::vector<Detection> output;
    for (int i = 0; i < nms_result.size(); ++i)
    {
        Detection result;
        int idx = nms_result.at(i);
        result.class_id = class_ids.at(idx);
        result.confidence = confidences.at(idx);
        result.class_name = coconame.at(result.class_id) + ' ' + std::to_string(result.confidence).substr(0, 4);
        result.box.width = boxes.at(idx).width * this->m_ratio;
        result.box.height = boxes.at(idx).height * this->m_ratio;
        result.box.x = (boxes.at(idx).x - 0.5 * boxes.at(idx).width - this->m_left_offset) * this->m_ratio ;
        result.box.y = (boxes.at(idx).y - 0.5 * boxes.at(idx).height - this->m_top_offset) * this->m_ratio ;

        output.push_back(result);
    }
    //绘制
    for (int i = 0; i < output.size(); ++i)
    {
        auto detection = output.at(i);
        auto box = detection.box;
        auto class_string = detection.class_name;

        float xmax = box.x + box.width;
        float ymax = box.y + box.height;

        //生成随机颜色
        // 获取当前系统时间作为种子
        auto current_time = std::chrono::system_clock::now().time_since_epoch().count();

        // 使用随机种子创建 RNG 对象
        cv::RNG rng(current_time);
        cv::Scalar color=  cv::Scalar(rng.uniform(100, 256),rng.uniform(100, 256),rng.uniform(100, 256));

        // Detection box
        cv::rectangle(frame, cv::Point(box.x, box.y), cv::Point(xmax, ymax), color, 2);

        // Detection box text
        cv::Size textSize = cv::getTextSize(class_string, cv::FONT_HERSHEY_DUPLEX, 1, 2, 0);
        cv::Rect textBox(box.x, box.y - 40, textSize.width + 10, textSize.height + 20);
        cv::rectangle(frame, textBox, color, cv::FILLED);
        cv::putText(frame, class_string, cv::Point(box.x + 5, box.y - 10), cv::FONT_HERSHEY_DUPLEX, 1, cv::Scalar(0, 0, 0));
    }
}

这个例子是ffmpeg读取的数据,用openVINO推理实现对每张图片进行实时推理,推理后的数据,按照顺序写入显示,保证数据顺序一致性。
在这里插入图片描述

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

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

相关文章

第三站:C/C++基础-二维数组

二维数组的概念 一维数组本身是多个大小相同的内存块,从0开始逐渐递增所组成的在横向上的有序"组合", 二维数组就是很多个一维数组在纵向上的组合,每一个一维数组就是二维数组在纵向上的从0开始的逐渐递增的一个单位,(所以一维数组在二维数组的基础上,每一个内存块…

AcWing1210-连号区间

文章目录 题目输入格式输出格式数据范围样例输入样例1输出样例1输入样例2输出样例2样例解释 思路代码 题目 输入格式 输出格式 数据范围 样例 输入样例1 4 3 2 4 1 输出样例1 7 输入样例2 5 3 4 2 5 1 输出样例2 9 样例解释 思路 固定L&#xff0c;遍历R在[L,R]区域中找到最大…

Java并发之互斥一:管程

1、简单聊聊什么是管程模型 &#xff08;共享资源&#xff09;&#xff1a;定义一个共享变量&#xff0c;可以理解锁&#xff0c;令牌这类的东西&#xff08;互斥访问共享资源&#xff09;&#xff1a;获取这个锁、令牌的时候是排好队的&#xff0c;只允许单线程访问&#xff…

《射雕三部曲》人物关系可视化及问答系统

背景&#xff1a; 该项目旨在构建一个基于图数据库和知识图谱的《射雕三部曲》人物关系可视化及问答系统。通过分析小说中的人物关系&#xff0c;将其构建成图数据库&#xff0c;并结合问答系统和数据分析技术&#xff0c;提供用户可视化的人物关系展示和相关问题的回答。 介绍…

快速排序-排序算法

算法思想 快速排序采用的仍然是分治的思想。 Step1.每次在无序的序列中选取一个基准数。 Step2.然后将大于和小于基准数的元素分别放置于基准数两边。&#xff08;前面部分的元素均小于或等于基准数&#xff0c;后面部分均大于或等于基准数&#xff09; Step3.然后采用分治法&…

mySQL 汇总

登录MySQL winR 打开查询命令 输入 cmd 输入net start MySQL 打开mysql 报错:系统错误&#xff0c;拒绝访问 &#xff08;没权限&#xff01;&#xff09; 解决办法&#xff1a;搜索栏查询‘cmd’ 使用管理员身份运行 &#xff08;或鼠标右键‘开始’&#xff0c;windows po…

YOLOv5 损失函数改进 | 引入 Shape-IoU 考虑边框形状与尺度的度量

🗝️改进YOLOv8注意力系列一:结合ACmix、Biformer、BAM注意力机制 论文讲解加入代码本文提供了改进 YOLOv8注意力系列包含不同的注意力机制以及多种加入方式,在本文中具有完整的代码和包含多种更有效加入YOLOv8中的yaml结构,读者可以获取到注意力加入的代码和使用经验,总…

学生评教,问卷调查表评价教师统计,python+pandas处理数据

先上一个结果表格 几个关键步骤 1、问卷网站上设置相关题目,条目,最好用评分题目(点击文本选项,但是保存下来的是分值),如图 2、pandas清洗数据,包括unstack,其目的是把所有学生得分细分展开,因为班级选科不同,问卷上针对的教师不同,比如有些学生需要评价物理教师…

关于‘ Mybatis中的动态SQL语句 ‘解析

1、什么是动态SQL MyBatis中的动态SQL是一种可以根据不同条件生成不同SQL语句的技术。它允许我们在映射文件中编写灵活的SQL语句&#xff0c;以便根据参数的不同情况来动态生成SQL语句。这种灵活性使得我们能够根据应用程序的需求来构建动态的查询语句。 2、动态SQL的作用 动…

如何在Github上快速下载代码

由于网络环境问题&#xff0c;有时候比较难从Github上下载代码&#xff0c;我归纳了以下三种从Github上下载代码的方法&#xff0c;如何选择使用&#xff0c;可根据你的实际情况&#xff1a; 目录 方法一&#xff1a;使用 “Download ZIP” 按钮 方法二&#xff1a;使用 Git…

使用AUTOSAR来开发汽车基础软件的优点

1、高质量。以前我们采用手写代码的方式&#xff0c;是几个工程师在战斗。现在我们采用平台&#xff0c;BSW代码都是供应商提供的&#xff0c;我们相当于后面还有一个团队陪着我们在战斗。 2、低成本。大家都说采用AUTOSAR平台好贵&#xff0c;但是从长远来看是值得的&#xff…

服务器感染了.pings勒索病毒,如何确保数据文件完整恢复?

导言&#xff1a; 随着科技的不断进步&#xff0c;网络犯罪也在不断演变。其中之一的.pings勒索病毒是一种危险的恶意软件&#xff0c;它能够加密用户的数据文件&#xff0c;并要求支付赎金以解密这些文件。在本文中&#xff0c;91数据恢复将介绍.pings勒索病毒&#xff0c;以…

回归预测 | Matlab基于SMA+WOA+SFO-LSSVM多输入单输出回归预测

回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测 目录 回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SMAWOASFO-LSSVM回归预测 基于黏菌算法鲸鱼算法向日葵算法优化LSSVM回归预测 其中包含三种改进…

常用机床类型的用途和介绍

随着市场对机加工需求的提升&#xff0c;机械加工的技术精度也随之提高&#xff0c;机床的种类也就越来越多。 根据加工方法和使用的工具进行分类&#xff0c;国家将机床编制为11类&#xff1a;车床、钻床、镗床、磨床、齿轮加工机床、螺纹加工机床、铣床、刨床、拔床、锯床等…

Frps服务端一键配置脚本搭建记录

安装&#xff0c;一般修改80&#xff0c;443端口&#xff0c;其他默认回车 Gitee wget https://gitee.com/mvscode/frps-onekey/raw/master/install-frps.sh -O ./install-frps.sh chmod 700 ./install-frps.sh ./install-frps.sh installGithub wget https://raw.githubuse…

2024美赛数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…

5个不买后悔的云服务器推荐(2024年更新)

作为多年站长使市面上大多数的云厂商的云服务器都使用过&#xff0c;很多特价云服务器都是新用户专享的&#xff0c;本文有老用户特价云服务器&#xff0c;阿腾云atengyun.com有多个网站、小程序等&#xff0c;国内头部云厂商阿里云、腾讯云、华为云、UCloud、京东云都有用过&a…

Linxu每日智囊

每日分享三个Linux命令,悄悄培养读者的Linux技能。 欢迎关注公众号(NLP Research) apt 作用 包管理器 语法 apt [选项] 软件包 参数: -h:帮助-y:当安装过程提示选择全部为"yes"-q:不显示安装的过程案例 列出所有可更新的软件清单命令sudo apt update升级软…

完整的模型训练套路(一、二、三)

搭建神经网络 model import torch from torch import nn#搭建神经网络 class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.model nn.Sequential(nn.Conv2d(3, 32, 5, 1, 2),nn.MaxPool2d(2),nn.Conv2d(32, 32, 5, 1, 2),nn.MaxPool2d(2),nn.Conv…

统计学-R语言-2.1

文章目录 前言安装过程总结 前言 上篇文章介绍了统计学-R语言的介绍&#xff0c;本篇文章介绍如何安装R软件。 安装过程 可以登录官网&#xff0c;https://www.r-project.org/&#xff0c;点击此处跳转。 点进去下滑找到China,之后找任意一个链接地址进行下载即可。 我点的是…