【Linux】生产者消费者模型——阻塞队列BlockQueue

news2024/9/21 4:39:01

文章目录

  • 一、生产者消费者模型
    • 生产消费理解
    • 生产消费关系
  • 二、基于blockqueue的生产和消费模型
    • 单生产单消费计算
      • 随机数
      • 计算器任务Task
      • 存储任务
    • 多生产多消费
  • 三、总结

一、生产者消费者模型

生产消费理解

引入:举个例子,比如我们学生想买东西,但是如果没有交易场所超市,那么我们只能去供货商去买东西,那我们只能如果要一件供货商只能生成一件,对于供货商来说生成的成本太大了,所以有了交易场所超市这个媒介的存在。目的就是为了集中需求,分发产品。

image-20230420084005988

消费者与生产者之间通过了超市进行交易。当生产者不需要的时候,供货商还可以继续声场,当供货商不再生产的时候消费者还能买得到!这样生产和消费就能进行解耦了。而我们把临时的宝成产品的场所称为缓冲区。

生产的过程和消费的过程——解耦

临时的保存产品的场所——缓冲区

当main函数调用函数func的时候,main函数会生产数据交给函数func,函数func会把数据暂时保存,而函数func会消费数据,这其实就是生产消费模型。但是当我们调用函数func的时候,main函数什么都不做,在那里阻塞等待函数返回,我们把main函数和调用函数func之间称为强耦合。

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

生产消费关系

image-20230420085851537

生产和消费都要看到“超市”,所以“超市”是一块共享资源。而既然是共享资源就会涉及到多线程访问,那么这块共享资源就要被保护起来

三种关系:生产者和生产者(互斥),消费者和消费者(互斥),生产者和消费者(互斥&&同步),互斥保证共享资源的安全性,,同步是为了提高访问效率

二种角色:生产者线程,消费者线程

一个交易场所:一段特定结构的缓冲区

想写生产消费模型,本质就是在维护321原则

挖掘特点

1.未来生产线程和消费线程进行解耦

2.支持生产和消费的一段时间的忙闲不均的问题(缓存区有数据有空间)

3.生产者专注生产,消费专注消费,提高效率

如果超市缓冲区满了,生产者只能进行等待,如果超市缓冲区为空,消费者只能进行等待。


二、基于blockqueue的生产和消费模型

阻塞队列:阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构

阻塞队列为空时,从阻塞队列中获取元素的线程将被阻塞,直到阻塞队列被放入元素。
阻塞队列已满时,往阻塞队列放入元素的线程将被阻塞,直到有元素被取出。

image-20230420093240803

单生产单消费计算

随机数

下面以单生产单消费为例子:

//BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =5;
template <class T>
class BlockQueue
{
public:
    BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_pcond,nullptr);
        pthread_cond_init(&_ccond,nullptr);
    }
    void push(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond,&_mutex);//因为生产条件不满足无法生产,此时我们的生产者进行等待
        }
        _q.push(in);
        //pthread_cond_signal:这个函数可以放在临界区内部,也可以放在外部
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
        //pthread_cond_signal(&_ccond);
    }
    void pop(T*out)//输出型参数,*,输入输出型:&
    {
        pthread_mutex_lock(&_mutex);
        while(is_empty())
        {
            pthread_cond_wait(&_ccond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond);
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty(){return _q.empty();}
    bool is_full(){return _q.size()==_maxcap;}
private:
    std::queue<T> _q;
    int _maxcap;//队列上限
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond;//生产者条件变量
    pthread_cond_t _ccond;//消费者条件变量
};

//mainCp.cc
void* consumer(void * bq_)
{
    BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(bq_);
    while(true)
    {
        int data;
        bq->pop(&data);
        std::cout<<"消费数据: "<<data<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void*productor(void*bq_)
{
    BlockQueue<int>*bq = static_cast<BlockQueue<int>*>(bq_);
    while(true)
    {
        int data = rand()%10+1;
        bq->push(data);
        std::cout<<"生产数据: "<<data<<std::endl;
    }
    return nullptr;
}
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    BlockQueue<int> *bq = new  BlockQueue<int>();
    pthread_t c,p;
    pthread_create(&c,nullptr,consumer,bq);
    pthread_create(&p,nullptr,productor,bq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    return 0;
}

pthread_cond_wait函数的第二个参数必须是我们正在使用的互斥锁,满了就会进行等待,如果像之前一样把锁拿走,那么其他线程就无法访问共享资源
a.pthread_cond_wait:该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起
b.pthread_cond_wait:该函数在被唤醒返回的时候,会自动的重新获取你传入的锁

pthread_cond_signal伪唤醒:判断的问题:假设生产者有10个,消费者只有一个,消费一下数据,如果是pthread_cond_broadcast是把10个线程同时唤醒,可是只需要生产一个数据,而同时把10个线程唤醒而如果是if判断的时候push就会出问题了

image-20230420105755836

如果生产者生产慢,消费者消费快:生产一个消费一个,而且消费的都是最新的数据

image-20230420112933795

如果生产者生产快,消费者消费慢:稳定后,消费一个生产一个

image-20230420113649157

计算器任务Task

Task.hpp:包含func_t的回调函数,这个函数就是进行数据计算的回调函数

#pragma once
#include <iostream>
#include <functional>
#include <cstdio>
class Task
{
    using func_t = std::function<int(int,int,char)>;
public:
    Task()
    {}
    Task(int x,int y,char op,func_t func)
    :_x(x),_y(y),_op(op),_callback(func)
    {}
    std::string operator()()
    {
        int result = _callback(_x,_y,_op);
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);
        return buffer;
    }
    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);
        return buffer;
    }
private:
    int _x;
    int _y;
    char _op;
    func_t _callback;
};

BlockQueue.hpp

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =500;
template <class T>
class BlockQueue
{
public:
    BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_pcond,nullptr);
        pthread_cond_init(&_ccond,nullptr);
    }

    void push(const T& in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond,&_mutex);
        }
        _q.push(in);
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
    }

    void pop(T*out)
    {
        pthread_mutex_lock(&_mutex);
        if(is_empty())
        {
            pthread_cond_wait(&_ccond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond);
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty(){return _q.empty();}
    bool is_full(){return _q.size()==_maxcap;}
private:
    std::queue<T> _q;
    int _maxcap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond;
    pthread_cond_t _ccond;
};

Main.cc

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
const std::string oper = "+-*/%"; 
int mymath(int x,int y,char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cerr << "div zero error!" << std::endl;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        break;
    }
    return result;
}
void* consumer(void * bq_)
{
    BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(bq_);
    while(true)
    {
        Task t;
        bq->pop(&t);
        std::cout<<"消费任务: "<<t()<<std::endl;
    }
    return nullptr;
}
void*productor(void*bq_)
{
    BlockQueue<Task>*bq = static_cast<BlockQueue<Task>*>(bq_);
    while(true)
    {
        int x = rand()%100+1;
        int y = rand()%10;
        int operCode = rand()%oper.size();
        Task t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"生产任务: "<<t.toTaskString()<<std::endl;  
        sleep(1);
    }
    return nullptr;
}
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    BlockQueue<Task> *bq = new  BlockQueue<Task>();
    pthread_t c,p;
    pthread_create(&c,nullptr,consumer,bq);
    pthread_create(&p,nullptr,productor,bq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    delete bq;
    return 0;
}

image-20230420121405826

存储任务

定义结构体BlockQueues封装计算任务的阻塞队列和存储任务的阻塞队列,创建生产者线程,消费者线程,存储线程执行各自的方法

image-20230420201304498

blockqueue.hpp

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap = 500;
template <class T>
class BlockQueue
{   
public:
    BlockQueue(const int &maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while(is_full())
        {
            pthread_cond_wait(&_pcond, &_mutex); 
        }
        _q.push(in);
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
    }
    void pop(T *out)
    {
        pthread_mutex_lock(&_mutex);
        while(is_empty())
        {
            pthread_cond_wait(&_ccond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond); 
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }
private:
    bool is_empty()
    {
        return _q.empty();
    }
    bool is_full()
    {
        return _q.size() == _maxcap;
    }
private:
    std::queue<T> _q;
    int _maxcap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond; 
    pthread_cond_t _ccond;
};

Task.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <functional>

class CalTask
{
    using func_t = std::function<int(int,int,char)>;
    // typedef std::function<int(int,int)> func_t;
public:
    CalTask()
    {}
    CalTask(int x, int y, char op, func_t func)
    :_x(x), _y(y), _op(op), _callback(func)
    {}
    std::string operator()()
    {
        int result = _callback(_x, _y, _op);
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);
        return buffer;
    }
    std::string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);
        return buffer;
    }
private:
    int _x;
    int _y;
    char _op;
    func_t _callback;
};

const std::string oper = "+-*/%";

int mymath(int x, int y, char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '*':
        result = x * y;
        break;
    case '/':
    {
        if (y == 0)
        {
            std::cerr << "div zero error!" << std::endl;
            result = -1;
        }
        else
            result = x / y;
    }
        break;
    case '%':
    {
        if (y == 0)
        {
            std::cerr << "mod zero error!" << std::endl;
            result = -1;
        }
        else
            result = x % y;
    }
        break;
    default:
        // do nothing
        break;
    }

    return result;
}

class SaveTask
{
    typedef std::function<void(const std::string&)> func_t;
public:
    SaveTask()
    {}
    SaveTask(const std::string &message, func_t func)
    : _message(message), _func(func)
    {}
    void operator()()
    {
        _func(_message);
    }
private:
    std::string _message;
    func_t _func;
};

void Save(const std::string &message)
{
    const std::string target = "./log.txt";
    FILE *fp = fopen(target.c_str(), "a+");
    if(!fp)
    {
        std::cerr << "fopen error" << std::endl;
        return;
    }
    fputs(message.c_str(), fp);
    fputs("\n", fp);
    fclose(fp);
}

MianCp.cc

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <ctime>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class K>
struct BlockQueues
{
    BlockQueue<C> *c_bq;
    BlockQueue<K> *s_bq;
};
void* productor(void*args)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;
    while(true)
    {
        int x = rand()%10+1;
        int y = rand()%5+1;
        int operCode = rand()%oper.size();
        CalTask t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void* consumer(void*args)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;
    while(true)
    {
        CalTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;

        SaveTask save(result,Save);
        save_bq->push(save);
        std::cout<<"cal thread,推送存储任务完成..."<<std::endl;
    }
    return nullptr;
}
void*saver(void*args)
{
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;
    while(true)
    {
        SaveTask t;
        save_bq->pop(&t);
        t();
        std::cout<<"save thread,保存任务完成..."<<std::endl;
    }
    return nullptr;
}
int main()
{
    srand((unsigned int)time(nullptr)^getpid());
    BlockQueues<CalTask,SaveTask> bqs;
    bqs.c_bq = new BlockQueue<CalTask>();
    bqs.s_bq = new BlockQueue<SaveTask>();
    pthread_t c,p,s;
    pthread_create(&p,nullptr,productor,&bqs);
    pthread_create(&c,nullptr,consumer,&bqs);
    pthread_create(&s,nullptr,saver,&bqs);
    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    pthread_join(s,nullptr);
    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

image-20230420181958543

多生产多消费

只需要稍微改一改MainCp.cc即可完成多生产多消费,其他文件代码不需要更改

MainCp.cc

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class S>
class BlockQueues
{
public:
    BlockQueue<C>*c_bq;
    BlockQueue<S>*s_bq;
}; 
void*productor(void*bqs_)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;
    while(true)
    {
        int x = rand()%100+1;
        int y = rand()%10;
        int operCode = rand()%oper.size();
        CalTask t(x,y,oper[operCode],mymath);
        bq->push(t);
        std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;
        sleep(1);
    }
    return nullptr;
}
void* consumer(void * bqs_)
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;

    while(true)
    {
        CalTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;

        // SaveTask save(result,Save);
        // save_bq->push(save);
        // std::cout<<"cal thread,推送存储任务完成..."<<std::endl;
        // sleep(1);
    }
    return nullptr;
}
void* saver(void* bqs_)
{
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;
    while(true)
    {
        SaveTask t;
        save_bq->pop(&t);
        t();
        std::cout<<"save thread,保存任务完成..."<<std::endl;
    }
    return nullptr;
};
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    BlockQueues<CalTask,SaveTask> bqs;
    bqs.c_bq = new  BlockQueue<CalTask>();
    bqs.s_bq = new  BlockQueue<SaveTask>();
    pthread_t c[2],p[3];
    pthread_create(p,nullptr,productor,&bqs);
    pthread_create(p+1,nullptr,productor,&bqs);
    pthread_create(p+2,nullptr,productor,&bqs);
    pthread_create(c,nullptr,consumer,&bqs);
    pthread_create(c+1,nullptr,consumer,&bqs);
  
    pthread_join(c[0],nullptr);
    pthread_join(c[1],nullptr);
    pthread_join(p[0],nullptr);
    pthread_join(p[1],nullptr); 
    pthread_join(p[2],nullptr);

    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

三、总结

多生产者多消费是可以的,这个阻塞队列在进程进入前要加锁,竞争这把锁。消费者与消费者也要竞争锁

**换句话来说:在阻塞队列中,无论外部线程再多,真正进入到阻塞队列里生产或消费的线程永远只有一个。**在一个任务队列中,有多个生产者与多个消费者,由于有锁的存在,所以任意时刻只有一个执行流在锁里面放。

生产消费模型高效在哪里:

对于生产者而言,要向blockQueue里面放置任务,对于消费者而言,要从blockQueue里面拿去任务

对于生产者,任务从哪里来?获取任务和构建任务是要花时间的

对于消费者,难道它把任务从任务队列中拿出来就完了吗?消费者拿到任务之后,后续还有没有任务?

高效体现在一个线程拿出来任务可能正在做计算,它在做计算的同时,其他线程可以继续从队列中拿,继续做运算,高效并不是体现在从队列中拿数据高效!而是我们可以让一个、多个线程并发的同时计算多个任务!在计算多个任务的同时,并不影响其他线程,继续从队列里拿任务的过程。也就是说,生产者消费者模型的高效:可以在生产之前与消费之后让线程并行执行,不要认为生产者消费模式仅仅只是把任务生产到队列的过程就是生产过程,生产过程:1.拿任务、需要费点劲2.拿到后再放到队列里面整个一体,整个生产的过程;整个消费的过程:不是把任务拿到线程的上下文中就完了,拿到之后还要进行计算或存储这些工作才是消费的过程在生产前和和消费后我们多个线程是可以并发的。

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

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

相关文章

Tomcat服务器

1.服务器概念&#xff1a; 服务器是一个容器&#xff0c;可以将任何资源放到服务器中&#xff0c;服务器启动后 外部用户可以通过 ip地址:端口/资源路径 来访问服务器容器内对应的资源 你可以将服务器理解为一个共享文件夹&#xff0c;只要服务器启动了&#xff0c;大家都可…

人社LEAF平台架构及其主要技术架构特点

人社LEAF平台架构及其主要技术架构特点https://wheart.cn/so/home?mindex&id31525d77-de79-11ed-96fa-52540016e6ac 在前面的系列文章中介绍了社会保险管理信息系统核心平台三版&#xff08;以下简称核三&#xff09;的技术亮点&#xff0c;这些技术亮点主要是由核三的技…

WebGIS:前端:给出地理范围计算出地图瓦片的行列号

目录 前端代码实现 根据xml配置文件计算出行列号 1、xml配置文件信息样例 2、代码实现 运用到的知识 该文档是根据本人做的项目进行的总结&#xff0c;可能存在知识不准确&#xff0c;仅做参考&#xff1b; 前端代码实现 根据提供一个瓦片服务地址&#xff0c;解析服务的…

【Linux】Linux入门手册

入门Linux Linux的目录结构Linux的远程操作Xshell 软件Xftp 软件 Linux 基础命令vi 和 vimLinux中的用户管理Linux中的组管理Linux中的权限管理文件或者目录中的三种权限修改文件或者目录的权限使用数字的方式修改文件或者目录的权限 Linux中的帮助命令Linux目录相关命令Linux查…

关于数制及其转换

关于数制及其转换 从1除以10谈起 十进制计算 1 10 0.1 商是有限小数 用二进制计算 是无限循环小数&#xff1a; 1 1010 0.00011001100110011…… 1/10 是无法用二进制小数精确表示的。十进制小数转换成二进制有可能无限循环。 十进制数0.1转换成二进制为0.00011000…

C++程序设计—类与对象

目录 1、类和对象的概念 2、面向对象程序设计的特点 3、类和对象的区别 4、成员运行算符 &#xff08;1&#xff09;&#xff08;.&#xff09;点运算符 &#xff08;2&#xff09;&#xff08;->&#xff09;箭头运算符 5、类的声明形式 &#xff08;1&#xff09;…

DataBinding 大坑总结(网上我暂时搜不到解决方法)

在使用多Module中使用DataBinding会引发一些奇怪的问题&#xff0c;最近好好的腾出时间来折腾这些奇怪的问题&#xff1a; 1&#xff1a;如果当Module启动DataBinding重启AS启动报错的话&#xff0c;就启用允许多行代码 android { defaultConfig {multiDexEnabled true} } de…

设计模式:UML中的类图(6种关系)

一.UML图介绍 统一建模语言是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。 UML 从目标系统的不同角度出发&#xff0c;定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。 二.类图…

apkanalyzer-classpath.jar 中没有.class 文件

apkanalyzer-classpath.jar 中没有.class 文件&#xff0c;apkanalyzer-classpath.jar 包目录下&#xff0c;只有 MANIFEST.MF 文件&#xff0c;如下截图&#xff1a; 而 apkanalyzer.jar 下&#xff0c;有很多 class 文件&#xff0c;其中&#xff0c;BinaryXmlParser.class 就…

P80-MySQL

//启动mysql&#xff0c;我的名字是mysql80 net start mysql80//我的端口号为3307 mysql -hlocalhost -P3307 -uroot -p一、课程介绍 什么是数据库? 数据库&#xff1a;DataBase&#xff08;DB&#xff09;&#xff0c;是存储和管理数据的仓库。

ChatGPT账号被封怎么办?进来看看解决办法

部分内容整理自网络&#xff0c;侵删 最近有很多同学说自己的chatgpt账号被封了。仔细研究了一下大部分被封账号&#xff0c;发现主要有这些个原因&#xff1a; 1&#xff0c;被封的账号可能是用程序批量注册的&#xff0c;也就是一台机器用一个IP在短时间内注册了大量的账号 …

JSON Web Tokens (JWT) — the only explanation you will ever need

本文摘抄自 Ariel Weinberger 博客 JSON Web Tokens (JWT) — the only explanation you will ever need | by Ariel Weinberger | Medium JSON Web Tokens (JWT) — the only explanation you will ever need JSON Web Tokens are changing the world for the better. Acting …

java程序员学前端-Vue2篇

Vue 2 1. Vue 基础 1) 环境准备 安装脚手架 npm install -g vue/cli-g 参数表示全局安装&#xff0c;这样在任意目录都可以使用 vue 脚本创建项目 创建项目 vue ui使用图形向导来创建 vue 项目&#xff0c;如下图&#xff0c;输入项目名 选择手动配置项目 添加 vue rou…

【Vue基础】element快速入门

一、知识点整理 1、安装Element UI组件库&#xff0c;在当前目录下&#xff0c;在命令行执行指令&#xff1a; npm install element -ui2.15.3 如果无法安装&#xff0c;执行以下指令&#xff1a; npm install --legacy-peer-deps element-ui --save 2、引入Element组件库 …

IT项目管理画图题【太原理工大学】

期末复习汇总&#xff0c;点这里&#xff01;https://blog.csdn.net/m0_52861684/category_12095266.html?spm1001.2014.3001.5482 也不知道让画啥&#xff0c;随便猜一下吧。我觉得大概率是让画双代号网络图了&#xff0c;不是网络图我倒立&#xff0c;呃...还是算了吧&#…

氢原子光谱、类氢原子光谱和类氢离子光谱

一、氢原子光谱 &#xff08;1&#xff09;万分之五的差值 在文章“原子的波尔模型、能量量子化、光电效应、光谱实验、量子态、角动量”的第3.3节角动量量子化中&#xff0c;通过公式联立获得得里德伯常数要比经验获得的相差万分之五。 当然这时候有人会想是不是实验测得不准…

设计模式:创建者模式 - 适配器模式

文章目录 1.概述2.结构3.类适配器模式4.对象适配器模式5.应用场景6.JDK源码解析 - Reader 与 InputStream 1.概述 如果去欧洲国家去旅游的话&#xff0c;他们的插座如下图最左边&#xff0c;是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑&#xff0c;手机…

中国人民大学与加拿大女王大学金融硕士——每一份投入和努力其实都有回声

有付出&#xff0c;就会有收获&#xff1b;有努力&#xff0c;就会有回报。当你愿意走出舒适区投入到再学习上&#xff0c;当你为了提升自身而努力后&#xff0c;你终将收获属于你的美好。在金融领域在职读研的我们&#xff0c;待拿到中国人民大学与加拿大女王大学金融硕士毕业…

zabbix配置钉钉机器人告警

1.在钉钉上创建一个钉钉群组 2.在群组中添加一个机器人 3.配置zabbix server调用钉钉接口的代码(使用python) 查看是否有python环境 python --version 找到zabbix 的AlertScriptsPath目录路径 cat /etc/zabbix/zabbix_server.conf|grep AlertScriptsPath 将调用钉钉接口的py…

51单片机(80951系列)引脚功能说明

一 AT89C51引脚图 1.0 中断 1.0.1 中断源 AT89C51一共有5个中断源 &#xff08;1&#xff09;&#xff1a;外部中断0&#xff0c;外部中断请求信号由引脚输入&#xff0c;低电平或下降沿有效&#xff0c;中断请求标志位IE0。 &#xff08;2&#xff09;&#xff1a;外部中断1…