【Linux】题解:生产者与消费者模型(附源代码)

news2024/9/22 15:51:36

【Linux】题解:生产者与消费者模型(附源代码)

摘要:本文主要介绍生产者与消费者模型,其中主要内容分为对该模型的介绍及分析,阻塞队列实现该模型,并对其升级实现多生产者多消费者并行执行。其中使用了信号量等方法,可以参考文章线程的同步与互斥。


文章目录

  • 【Linux】题解:生产者与消费者模型(附源代码)
    • 一、概述
    • 二、模型分析
    • 三、基于阻塞队列的实现
      • 3.1程序基本框架
      • 3.2 临界资源保护与条件变量设置
      • 3.3 业务完成
    • 四、基于环形队列的实现
      • 4.1 环形队列
      • 4.2 资源保护与变量设置
      • 4.3 环形队列实现
      • 4.4 业务代码实现

一、概述

所谓生产者与消费者模型,不可简单的理解为生产者生产后消费者对其进行消费的一对一模型,在学习线程后,与过往的函数调用比较,函数调用时把数据传入另一个函数中的参数中,在另一个函数中运行,在将其返回。该过程是一个串行的过程,可是在学习线程后则是可以通过一个临界区域完成数据的传递,并达成生产消费模型。其提高效率的本质就是将生产环节和消费环境进行解耦

**生产者消费者模式通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过临界资源来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给临界资源临界资源,消费者不找生产者要数据,而是直接从临界资源里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个临界资源就是用来给生产者和消费者解耦的。**示意图如下:

以生活中常见的超市作为例子。所谓生产者是对应的货品供货商,而临界资源则是超市,我们即是消费者。供应商供应货品到超市中,不用等待消费者来消费才会对商品处理,消费者只需要在超市中进行获取即可。其中超市平衡了生产者与供货商的处理能力,完成了顾客与供货商直接的解耦操作,可以并行的执行。

二、模型分析

对于关系分析,可以得出关于各个对象之间的关系,对于各个供应商而言,每个供应商共同需要访问的临界资源,因此对应的就是竞争互斥关系,而各个消费者亦然。对于生产者和消费者直接则是存在顺序问题,生产者生产后才可以给消费者供应,当然同时也需要访问临界资源,因此为同步互斥关系。关系总结如下:

  • 各个生产者:竞争互斥关系
  • 各个消费者:竞争互斥关系
  • 生产者和消费者:同步互斥关系

而对于执行流的分析,则是存在两个身份,分别为生产者与消费者,因此在创建线程时,创建的只有两种执行流。对于临界资源,可以为内存空间,可以为stl容器等。

三、基于阻塞队列的实现

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出,示意图如下:

3.1程序基本框架

// BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>
namespace ns_blockqueue{
    template <class T>
    class BlockQueue{
    private:
        std::queue<T> blockQueue_;  // 阻塞队列
        int cap;    // 容量
    public:
        BlockQueue(){}
        ~BlockQueue(){}
    public:
        void Push(){
            // 生产
        }
        void Pop(){
            // 消费
        }
    };
}
// consumer_productor.cc
#include "BlockQueue.hpp"

using namespace ns_blockqueue;

// 消费者业务
void* consumerRun(void* args){
    BlockQueue<int> *blockqueue = (BlockQueue<int>*)args;
    while(true){
        blockqueue->Pop();
    }
}
// 生产者业务
void* productorRun(void* args){
    BlockQueue<int> *blockqueue = (BlockQueue<int>*)args;
    while(true){
        blockqueue->Push();
    }
}

int main(){
    // 阻塞队列
    BlockQueue<int> *blockqueue = new BlockQueue<int>();
    // 线程的创建与销毁:两个执行流——生产者与消费者
    pthread_t consumer,productor;
    pthread_create(&consumer,nullptr,consumerRun,(void*)blockqueue);
    pthread_create(&productor,nullptr,productorRun,(void*)blockqueue);

    pthread_join(consumer,nullptr);
    pthread_join(productor,nullptr);
    return 0;
}

3.2 临界资源保护与条件变量设置

在模型分析的过程中,需要对临界资源进行保护以及对生产者与消费者进行同步设置。对于临界资源的保护,通过互斥锁设置即可。而条件变量来完成同步的设置,首先只有生产才可以知道消费者什么时候进行消费,消费者知道,生产者什么时候可以进行生产。在临界资源生产满后,不再生产了让消费者消费,当临界资源消费空了,就不能够消费了,而是需要生产者生产。而生产者在生产后可以唤醒消费者消费,同理消费者消费后可以唤醒生产者进行生产。

为此定义一个互斥锁以及两个条件变量完成对线程的同步,以下代码包括了对各个变量的初始化与销毁以及对上述逻辑的代码编写,代码示例如下:

// BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
namespace ns_blockqueue{

    const int default_capacity = 10;

    template <class T>
    class BlockQueue{
    private:
        std::queue<T> blockQueue_;  // 阻塞队列
        int cap_;    // 容量
        pthread_mutex_t mtx_;    // 临界资源保护:互斥量
        pthread_cond_t is_full_;    // 阻塞队列满,生产者进行等待
        pthread_cond_t is_empty_;   // 阻塞队列空,消费者进行等待
    private:
        bool IsFull(){
            return blockQueue_.size() == cap_;
        }
        bool IsEmpty(){
            return blockQueue_.size() == 0;
        }
        void LockQueue(){
            pthread_mutex_lock(&mtx_);
        }
        void UnLockQueue(){
            pthread_mutex_unlock(&mtx_);
        }
        void ProductorWait(){
            pthread_cond_wait(&is_full_,&mtx_);
        }
        void ConsumerWait(){
            pthread_cond_wait(&is_empty_,&mtx_);
        }
        void WakeupConsumer(){
            pthread_cond_signal(&is_empty_);
        }
        void WakeupProductor(){
            pthread_cond_signal(&is_full_);
        }
    public:
        // 互斥量与条件变量初始化与销毁
        BlockQueue(int cap_ = default_capacity):cap_(cap_){
            pthread_mutex_init(&mtx_,nullptr);
            pthread_cond_init(&is_empty_,nullptr);
            pthread_cond_init(&is_full_,nullptr);
        }
        ~BlockQueue(){
            pthread_mutex_destroy(&mtx_);
            pthread_cond_destroy(&is_empty_);
            pthread_cond_destroy(&is_full_);
        }
    public:
        void Push(const T &in){
            // 生产
            LockQueue();
            while(IsFull()){
                ProductorWait();
            }
            blockQueue_.push(in);
            UnLockQueue();
            WakeupConsumer();
        }
        void Pop(T *out){
            // 消费
            LockQueue();
            while(IsEmpty()){
                ConsumerWait();
            }
            *out = blockQueue_.front();
            blockQueue_.pop();
            UnLockQueue();
            WakeupProductor();
        }
    };
}

注意:此处在判断空与满时,是通过while循环判断的,这是为了防止存在挂起失败或者伪唤醒的情况。if判断不完善的原因在于,当出现上述问题时,实际上阻塞队列可能仍然处于满或者空的状态,此时会使得程序崩溃或者越界访问。

3.3 业务完成

随机数传输:通过产生随机数后,生产者将其产生于临界资源中,消费者从临界资源中进行消费,获取随机数。代码如下:

// consumer_productor.cc
#include "BlockQueue.hpp"
#include <cstdlib>
#include <time.h>
#include <unistd.h>
using namespace ns_blockqueue;
using namespace std;

// 消费者业务
void* consumerRun(void* args){
    BlockQueue<int> *blockqueue = (BlockQueue<int>*)args;
    while(true){
        sleep(2);
        int data = 0;
        blockqueue->Pop(&data);
        cout << "Consumer ID: " << pthread_self() << " consume number: " << data << endl;
    }
}
// 生产者业务
void* productorRun(void* args){
    BlockQueue<int> *blockqueue = (BlockQueue<int>*)args;
    while(true){
        int data = rand()%10 + 1;
        cout << "Productor ID: " << pthread_self() << " product number: "<< data << endl;
        blockqueue->Push(data);
        sleep(1);
    }
}

int main(){
    // 随机数产生
    srand((long long)time(nullptr));
    // 阻塞队列
    BlockQueue<int> *blockqueue = new BlockQueue<int>();
    // 线程的创建与销毁:两个执行流——生产者与消费者
    pthread_t consumer,productor;
    pthread_create(&consumer,nullptr,consumerRun,(void*)blockqueue);
    pthread_create(&productor,nullptr,productorRun,(void*)blockqueue);

    pthread_join(consumer,nullptr);
    pthread_join(productor,nullptr);
    return 0;
}
[root@VM-12-7-centos consumer_productor]# ./consumer_productor 
Productor ID: 140250556012288 product number: 1
Productor ID: 140250556012288 product number: 6
Consumer ID: 140250564404992 consume number: 1
Productor ID: 140250556012288 product number: 4
Productor ID: 140250556012288 product number: 9

任务派发与实现:生产者消费者模型不单单只是数据的传输,还有数据的处理过程,在此对其进行该场景进行实现。该实例是生产者派送关于随机数计算任务到临界区中,消费者获取任务并进行打印结构。代码如下:

// Task.hpp
#pragma once

#include <iostream>
#include <iomanip>
namespace ns_task{
    class Task{
    public:
        int x_;
        int y_;
        char op_;    // + - * / %
    public:
        Task(){
        }
        Task(int x,int y,char op){
            x_ = x;
            y_ = y;
            op_ = op;
        }
        ~Task(){}
        int Run(){
            int res = 0;
            switch (op_)
            {
            case '+':
                res = x_ + y_;
                break;
            case '-':
                res = x_ - y_;
                break;
            case '*':
                res = x_ * y_;
                break;
            case '/':
                res = x_ / y_;
            break;
            case '%':
                 res = x_ % y_;
                break;  
            default:
                std::cout <<"Task Error\n";
                return -1;
                break;
            }
            std::cout << std::left << std::setw(2) << "Task ---> "<< x_ << " " << op_ << " " << y_ << " = " << res <<std::endl;
            return 0;
        }
        int operator()(){
            return Run();
        }
    };
}
// consumer_productor.cc
#include "BlockQueue.hpp"
#include <cstdlib>
#include <time.h>
#include <unistd.h>
#include "Task.hpp"
#include <string.h>
#include <iomanip>

using namespace ns_blockqueue;
using namespace std;
using namespace ns_task;

// 消费者业务
void* consumerRun(void* args){
    BlockQueue<Task> *blockqueue = (BlockQueue<Task>*)args;
    while(true){
        sleep(2);
        Task task;
        blockqueue->Pop(&task);
        cout << std::left << setw(2)  << "Consumer ID: " << pthread_self();
        task();
    }
}
// 生产者业务
void* productorRun(void* args){
    BlockQueue<Task> *blockqueue = (BlockQueue<Task>*)args;
    std::string ops = "+-*/%";
    while(true){
        int x = rand()%20 + 1;
        int y = rand()%20 + 1;
        char op = ops[rand()%5];
        cout << std::left << setw(2) << "Productor ID: " << pthread_self() << " product task: "<< x << " " << op << " " << y << endl;
        Task task(x,y,op);
        blockqueue->Push(task);
        sleep(1);
    }
}

int main(){
    // 随机数产生
    srand((long long)time(nullptr));
    // 阻塞队列
    BlockQueue<Task> *blockqueue = new BlockQueue<Task>();
    // 线程的创建与销毁:两个执行流——生产者与消费者
    pthread_t consumer1,productor;
    pthread_t consumer2;
    pthread_t consumer3;
    pthread_t consumer4;
    pthread_create(&consumer1,nullptr,consumerRun,(void*)blockqueue);
    pthread_create(&consumer2,nullptr,consumerRun,(void*)blockqueue);
    pthread_create(&consumer3,nullptr,consumerRun,(void*)blockqueue);
    pthread_create(&consumer4,nullptr,consumerRun,(void*)blockqueue);

    pthread_create(&productor,nullptr,productorRun,(void*)blockqueue);

    pthread_join(consumer1,nullptr);
    pthread_join(consumer2,nullptr);
    pthread_join(consumer3,nullptr);
    pthread_join(consumer4,nullptr);
    pthread_join(productor,nullptr);
    return 0;
}
[root@VM-12-7-centos consumer_productor]# ./CPTask 
Productor ID: 140038218860288 product task: 19 + 16
Productor ID: 140038218860288 product task: 4 + 11
Consumer ID: 140038235645696 Working Task---> 19 + 16 = 35
Consumer ID: 140038227252992 Working Task---> 4 + 11 = 15
Productor ID: 140038218860288 product task: 6 * 20
Consumer ID: 140038252431104 Working Task---> 6 * 20 = 120

四、基于环形队列的实现

4.1 环形队列

在实现前先认识一下需要使用的数据结构——环形队列,环形队列是一种队列的变形,是固定大小的队列,在逻辑方面呈环状结构。一般实现方式有两种,分别为计数器来统计队列中的分布状况,也可以使用镂空一个位置来分辨队列的分布情况,此处主要介绍第一种。通过计数器与队列容量的大小比较来获知队列分布状态。

在此使用该数据结构,来完成对多线程情况下的并发访问临界资源,实现一个基于环形队列的生产者消费者模型。

4.2 资源保护与变量设置

在该模型中是存在互斥和同步特性的,在生产者和消费者开始时,指向同一位置表示队列为空的时候,应该让生产者进行生产,在生产者和消费者队列为满时,也是处于同一位置,此时需要让消费者先访问。当队列不为空或满时,生产者和消费者一定指向不同的位置,此时就是生产者和消费者可以并发执行。并且对于生产者或者消费者而言,不能够对同一身份的对象进行同一区域的访问,为此说明了该模型下的互斥特性。

可以得出结论,设计为生产者关心的资源是空余位置,而消费者关心的资源是队列中的数据,为此可以使用信号量对其进行同步互斥的设定。还需要注意的是,在多生产和多消费的情况下,使用信号量完成对同步和互斥的设定是对于整块临界资源的保护,但是由于在某块区域的写入读取过程中,可能无法保护,在此设定了互斥锁进行保护。

总结如下:

  • 信号量sem_blank:表示队列中空闲临界资源的大小,在生产者向队列写入数据后为生成空闲空间,进行P操作,在信号量不足时,表示队列满,生产者需要进行等待,在消费者消费数据后进行信号量释放,为V操作
  • 信号量sem_data:表示队列中临界资源的大小,在生产者向队列写入数据后进行V操作,在信号量不足时,表示队列为空,消费者需要进行等待,在消费者消费数据时进行信号量申请,为P操作
  • 互斥锁mutex_consumer:消费者间的竞争关系
  • 互斥锁mutex_productor:生产者间的竞争关系

4.3 环形队列实现

以下代码为唤醒队列类的构建,具体注意内容如下:

  • 通过索引来确定生产消费的位置,由于是环形队列,因此索引需要注意更新,防止越界等问题,方法为取模运算
  • 信号量和互斥锁的运用主要在于生产消费接口中,内容即是对分析内容的实现
  • 信号量与互斥锁是存在顺序关系的,主要原因是,互斥锁如果在信号量前加锁,程序不会出现问题,但是是串行执行的方式运行程序,多线程显得无意义。如果在之后加锁,说明为先申请信号量,在对于局部的临界资源进行互斥访问,可以达到并行的目的。
  • 在构造和析构函数中可以完成对应的变量初始化
// ringQueue.hpp
#pragma once

#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>

namespace ns_ringQueue
{
    // 默认容量
    const int default_capacity = 10;

    template<class T>
    class RingQueue{
    private:
        std::vector<T> ring_queue_;     // 队列结构
        int capacity_;  // 容量
        // 信号量与互斥锁
        pthread_mutex_t mutex_consumer_;
        pthread_mutex_t mutex_productor_;
        sem_t sem_blank_;
        sem_t sem_data_;
        // 生产者与消费者的索引
        int consumer_index_;
        int productor_index_;
    public:
        RingQueue(int capacity_ = default_capacity):ring_queue_(capacity_),capacity_(capacity_){
            // 数据初始化
            sem_init(&sem_blank_,0,capacity_);
            sem_init(&sem_data_,0,0);
            pthread_mutex_init(&mutex_consumer_,nullptr);
            pthread_mutex_init(&mutex_productor_,nullptr);
            consumer_index_ = 0;
            productor_index_ = 0;
        }
        ~RingQueue(){
            // 销毁
            sem_destroy(&sem_blank_);
            sem_destroy(&sem_data_);
            pthread_mutex_destroy(&mutex_consumer_);
            pthread_mutex_destroy(&mutex_productor_);
        }
    public:
        void Push(const T &in){
            // 生产接口
            sem_wait(&sem_blank_);  //申请信号量

            pthread_mutex_lock(&mutex_productor_);  //上锁:局部临界资源
           
            ring_queue_[productor_index_] = in;     //写入
            productor_index_++;     // 索引更新
            productor_index_ %= capacity_;

            pthread_mutex_unlock(&mutex_productor_);    //解锁
            
            sem_post(&sem_data_);   // 释放信号量
        }
        void Pop(T *out){
            // 消费接口
            sem_wait(&sem_data_);   //申请信号量
            
            pthread_mutex_lock(&mutex_consumer_);   //上锁:局部临界资源
            
            *out = ring_queue_[consumer_index_];    //输出
            consumer_index_++;
            consumer_index_ %= capacity_;   //索引更新
            
            pthread_mutex_unlock(&mutex_consumer_);     //解锁
            
            sem_post(&sem_blank_);      // 释放信号量
        }
    };
}

4.4 业务代码实现

业务实现:对于多生产者生产任务到循环队列中,对于多消费消费任务于信号队列中,并打印提示信息。该部分内容哦与阻塞队列的业务实现第二个实例相似,在此不做过多介绍,代码实例如下:

// ringConsumerProductor.cc
#include "ringQueue.hpp"
#include <unistd.h>
#include <pthread.h>
#include "Task.hpp"
using namespace ns_ringQueue;
using namespace std;
using namespace ns_task;
void* consumerRountine(void *args){
    RingQueue<Task> *rq = (RingQueue<Task> *)args;
    while(true){
        Task task;
        rq->Pop(&task);
        task();
        sleep(2);
    }
}
void* productorRountine(void *args){
    RingQueue<Task> *rq = (RingQueue<Task> *)args;
    std::string ops = "+-*/%";
    while(true){
        int x = rand()%20 + 1;
        int y = rand()%20 + 1;
        char op = ops[rand()%5];
        cout << std::left << setw(2) << "Productor ID: " << pthread_self() << " product task: "<< x << " " << op << " " << y << endl;
        Task task(x,y,op);
        rq->Push(task);
        sleep(1);
    }
}
int main(){
    RingQueue<Task> *rq = new RingQueue<Task>();

    pthread_t consumer1;
    pthread_t consumer2;
    pthread_t consumer3;
    pthread_t consumer4;

    pthread_t productor1;
    pthread_t productor2;
    pthread_t productor3;

    pthread_create(&consumer1,nullptr,consumerRountine,(void *)rq);
    pthread_create(&consumer2,nullptr,consumerRountine,(void *)rq);
    pthread_create(&consumer3,nullptr,consumerRountine,(void *)rq);
    pthread_create(&consumer4,nullptr,consumerRountine,(void *)rq);

    pthread_create(&productor1,nullptr,productorRountine,(void *)rq);
    pthread_create(&productor2,nullptr,productorRountine,(void *)rq);
    pthread_create(&productor3,nullptr,productorRountine,(void *)rq);

    pthread_join(consumer1,nullptr);
    pthread_join(consumer2,nullptr);
    pthread_join(consumer3,nullptr);
    pthread_join(consumer4,nullptr);

    pthread_join(productor1,nullptr);
    pthread_join(productor2,nullptr);
    pthread_join(productor3,nullptr);
    return 0;
}
// Task.hpp
#pragma once

#include <iostream>
#include <iomanip>
namespace ns_task{
    class Task{
    public:
        int x_;
        int y_;
        char op_;    // + - * / %
    public:
        Task(){
        }
        Task(int x,int y,char op){
            x_ = x;
            y_ = y;
            op_ = op;
        }
        ~Task(){}
        int Run(){
            int res = 0;
            switch (op_)
            {
            case '+':
                res = x_ + y_;
                break;
            case '-':
                res = x_ - y_;
                break;
            case '*':
                res = x_ * y_;
                break;
            case '/':
                res = x_ / y_;
            break;
            case '%':
                 res = x_ % y_;
                break;  
            default:
                std::cout <<"Task Error\n";
                return -1;
                break;
            }
            std::cout << std::left << std::setw(2) << "Task Running ---> "<< x_ << " " << op_ << " " << y_ << " = " << res <<std::endl;
            return 0;
        }
        int operator()(){
            return Run();
        }
    };
}
[root@VM-12-7-centos Blog_sem]# ./ringConsumerProductor 
Productor ID: 139731206522624 product task: 4 * 7
Task Running ---> 4 * 7 = 28
Productor ID: 139731214915328 product task: 16 + 14
Task Running ---> 16 + 14 = 30
Productor ID: 139731198129920 product task: 7 % 13
Task Running ---> 7 % 13 = 7
Productor ID: 139731206522624 product task: 2 * 3
Productor ID: 139731214915328 product task: 11 / 20

补充:

  1. 代码将会放到:Linux_Review: Linux博客与代码 (gitee.com) ,欢迎查看!
  2. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!

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

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

相关文章

Day14 基于AOP的声明式事务控制

1 Spring 事务编程概述PlatformTransactionManager TransactionDefinition TransactionStatus2 搭建环境数据库准备一个账户表tb account;dao层准备一个AccountMapper&#xff0c;包括incrMoney和decrMoney两个方法;service层准备一个transferMoney方法&#xff0c;分别调用in…

18行列式及其性质

从此课开始&#xff0c;就进入了这门课的第二部分。迄今为止&#xff0c;已经学习了很多关于长方矩阵的知识&#xff0c;现在&#xff0c;把注意力转向方阵&#xff0c;探讨两个大的话题&#xff1a;行列式和特征值&#xff0c;需要行列式的重要原因是求特征值。 行列式是跟每…

U3772频谱分析仪

18320918653 U3772 新的便携式频谱分析仪具有体积小&#xff0c;重量轻的特点&#xff0c;可以在微波和毫米波范围内测量无线信号日本株式会社爱德万测试是全球半导体测试系统的领先企业&#xff0c;于2005年7月6日发布了一种新的便携式频谱分析仪U3771&#xff08;频率达到3…

[Java-多线程] 锁原理(轻量级锁、锁膨胀、自旋锁、偏向锁)

1.Java对象头 我们平时使用的对象都是由两部分组成, 第一部分是对象头, 第二部分是对象的成员变量, 这里我么主要讲解对象头, 以32为虚拟机为例 : Object Header (64 bits)Mark Word (32 bits)Klass Word (32 bits)Klass Word : 每个对象都有类型 通过Klass Word就可以找到对应…

离散数学与组合数学-07命题逻辑

文章目录离散数学与组合数学-07命题逻辑7.1 命题逻辑-什么是命题7.1.1 数理逻辑发展7.1.2 什么是命题7.1.3 复合命题7.2 命题逻辑-命题联结词7.2.1 否定联结词7.2.2 合取联结词7.2.3 析取联结词7.2.4 蕴涵联结词7.2.5 等价联结词7.3 命题逻辑-命题符号化及应用7.3.1 联结词总结…

什么是JMM模型

什么是JMM模型&#xff1f;Java内存模型(Java Memory Model简称JMM)是一种抽象的概念&#xff0c;并不真实存在&#xff0c;它描述的是一组规则或规范&#xff0c;通过这组规范定义了程序中各个变量&#xff08;包括实例字段&#xff0c;静态字段和构成数组对象的元素&#xff…

11. 线程本地变量ThreadLocal的使用

1. 对ThreadLocal的理解 ThreadLocal&#xff0c;有人称它为线程本地变量&#xff0c;也有人称它为线程本地存储&#xff0c;其实表达的意思是一样的。ThreadLocal在每一个变量中都会创建一个副本&#xff0c;每个线程都可以访问自己内部的副本变量。 在多线程环境下&#xff…

tomcat 的并发能力分析

tomcat 参考&#xff1a;Tomcat的3个参数acceptCount、maxConnections、maxThreads Tomcat 的核心组件 Tomcat 由 2 大核心组件组成&#xff1a;Connector、Container Tomcat 处理请求的过程 请求在 tomcat 服务器的处理过程&#xff08;BIO 模式&#xff09; 客户端与服务…

嵌入式串行通信协议

嵌入式系统中&#xff0c;不同芯片之间通常使用串行总线的方式进行连接&#xff0c;根据器件行业规范、应用场景&#xff0c;不同芯片通常选择不同的串行通信接口进行通信。常用的串行通信接口有&#xff1a;1-Wire、I2C、SPI、UART。 一、1-Wire 1-wire单总线是Maxim的全资子…

bodgeito通关教程

6.bodgeito通关教程 进入网站整体浏览网页 点击页面评分进入关卡 一般搭建之后这里都是红色的&#xff0c;黄色是代表接近&#xff0c;绿色代表过关 首先来到搜索处本着见框就插的原则 构造payload输入 <script> alert(/xss/)</script>成功弹窗xss&#xff0c;发…

Redis数据库

1.Redis简介 1.1Redis简介 Redis 是当前互联网世界最为流行的 NoSQL&#xff08;Not Only SQL&#xff09;数据库。NoSQL 在互联网系统中的作用很大&#xff0c;因为它可以在很大程度上提高互联网系统的性能。 Redis 具备一定持久层的功能&#xff0c;也可以作为一种缓存工具…

c++实现堆排序

看了一下优先队列&#xff0c;查了一下堆排序。堆排序主要就是建最大堆&#xff08;最小堆&#xff09;和交换2个操作。如果建的是最大堆&#xff0c;那么交换的时候&#xff0c;父节点就和最大的子节点比较&#xff0c;如果它比最大的子节点还大&#xff0c;那就不用比了。因为…

STM32MP157开发板Linux+Qt项目实战:智能猫眼

stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器&#xff0c;集成2Cortex-A7核和1个Cortex-M4 核&#xff0c;A7核上可以跑Linux操作系统&#xff0c;M4核上可以跑FreeRTOS…

docker 搭建postgres 主从 pgadmin

准备工作 创建一个docker bridge 网路用于测试 docker network create -d bridge --subnet 192.168.0.0/24 --gateway 192.168.0.1 pgnetwork # 查看 docker network ls 设置了网段为 192.168.0.0&#xff0c;规划主从库IP端口如下&#xff1a; 主库 192.168.0.101:5432 从库…

Ubuntu16.04安装深度学习环境(CUDA9.2+PyTorch0.4.1+Python2.7)

之前已经安装好了显卡驱动&#xff0c;接着就可以安装CUDA了于是又找了好几篇文章进行参考&#xff1a;https://zhuanlan.zhihu.com/p/361190040https://blog.csdn.net/qq_43665602/article/details/125752433https://blog.csdn.net/myg22/article/details/84029924https://blo…

二、数据缓存

文章目录数据缓存1.标准缓存流程2.缓存更新一致性3.缓存穿透解决方案缓存空对象布隆过滤器4.缓存雪崩解决方案5.缓存击穿解决方案互斥锁逻辑过期6.使用函数式接口封装工具类学习 黑马点评项目整理总结: https://www.bilibili.com/video/BV1cr4y1671t/?vd_source5f3396d3af2c39…

webpack项目配置

30.webpack——webpack5新特性&#xff08;启动、持久化缓存、资源模块、URIs、moduleIds和chunkIds、tree shaking、nodeJs的polyfill被移除、模块联邦&#xff09;_俞华的博客-CSDN博客_chunkids webpack和vite的区别 - 简书 vite介绍 &#xff5c; 与其他构建工具做比较&…

【ONE·C || 字符串和内存函数】

总言 C语言&#xff1a;字符串和内存函数使用介绍。 文章目录总言1、求字符串长度&#xff1a;strlen1.1、基本介绍1.2、演示说明1.2.1、strlen输出1.2.2、strlen返回值1.3、模拟实现strlen1.3.1、计数器写法1.3.2、递归写法1.3.3、指针-指针写法2、长度不受限制的字符串函数2.…

educoder数据结构 图 无错AC代码版

目录 第1关&#xff1a;实现图的宽度优先遍历 任务描述 相关知识 编程要求 测试说明 输入输出格式说明&#xff1a; 样例输出 Ac_Code 第2关&#xff1a;实现图的深度优先遍历 任务描述 相关知识 测试说明 输入输出格式&#xff1a; 样例输出 AC_Code 第1关&am…

2023年flag

开头总是让人那么茫然无措&#xff0c;在这里记录梳理上一年。以期找到前进的方向&#xff0c;迈开新一年的第一步&#xff0c;然后不断前行。 回顾上一年 首先想到的第一件事&#xff0c;11月换了个工作依然是Java开发10月份准备了软件工程中级考试并考过读了几本技术的书籍…