Linux C++ 200行完成线程池类

news2024/10/2 3:25:42

文章目录

  • 1、atomic使用
  • 2、volatile关键字
  • 3、条件变量
  • 4、成员函数指针使用
  • 5、线程池
  • 6、主线程先退出对子线程影响
  • 7、return、exit、pthread_exit区别
  • 8、进程和线程的区别

1、atomic使用

原子操作,不可分割的操作,要么完整,要么不完整。

#include <pthread.h>
#include <unistd.h>
#include <iostream>
#include <atomic>
using namespace std;

atomic<int> g_acount;
int g_count = 0;
void* ThreadFunc(void* threadData)
{
    for(int i=0;i<1000000;i++)
    {
        g_count++;
        g_acount++;
    }
}

int main(int argc, const char** argv) 
{
    pthread_t pid1,pid2;
    int err = pthread_create(&pid1,NULL,ThreadFunc,NULL);
    if(err!=0)
    {
        cout<<"thread fail---"<<endl;
        exit(0);
    }
    err = pthread_create(&pid2,NULL,ThreadFunc,NULL);
    if(err!=0)
    {
        cout<<"thread fail---"<<endl;
        exit(0);
    }
    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    cout<<"g_count:"<<g_count<<endl;
    cout<<"g_acount:"<<g_acount<<endl;
    return 0;
}

makefile

all: pthreadText

pthreadText:pthreadText.cpp
	g++ -o pthreadText pthreadText.cpp -pthread -std=c++11

运行结果:
在这里插入图片描述

2、volatile关键字

用volatile关键字声明的变量,会告诉编译器,这个变量随时可能发生变化,编译器在编译的时候就不会对变量进行激进的优化,每次去读取的时候都会去内存中取,相反,如果编译器进行量优化,可能读取的时候去寄存器去读取这个值,三种特性:易变的、不可优化的、顺序执行的。

3、条件变量

条件本身(while((g_msgQueue.size() == 0) && isRuning == false))是由互斥量保护的,线程在发生改变之前首先锁住互斥量,其他线程不会察觉到这种改变,因为互斥量必须锁住才能计算条件。

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <list>
using namespace std;

// 初始化
pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

struct msgStr
{
    char name[256];
    int age;
    int id;
};

list<msgStr*> g_msgQueue;
bool isRuning = false;

void* outCache(void* data)
{
    while(true){
        pthread_mutex_lock(&g_mutex);
        while((g_msgQueue.size() == 0) && isRuning == false){
            pthread_cond_wait(&g_cond,&g_mutex);
        }
        if(isRuning){
            pthread_mutex_unlock(&g_mutex);
            break;
        }
        // 消息处理
        msgStr* jobbuf= g_msgQueue.front();
        g_msgQueue.pop_front();
        pthread_mutex_unlock(&g_mutex);

        // 消息处理
        cout<<"tid:"<<pthread_self()
            <<"name:"<<jobbuf->name
            <<"age:"<<jobbuf->age
            <<"id:"<<jobbuf->id
            <<endl;
        usleep(1000);
        delete jobbuf;
        jobbuf = NULL;
    }
}

void inCache(int sig)
{
    // 收到15这个信号,向消息队列中添加数据
    if(sig == 15){
        struct msgStr* msg = NULL;
        pthread_mutex_lock(&g_mutex);
        for(int i=0;i<1000;i++){
            msg = new msgStr();
            sprintf(msg->name,"name--%d",i);
            msg->age = i;
            msg->id = 1000+i;
            g_msgQueue.push_back(msg);
        }
        pthread_mutex_unlock(&g_mutex);
        pthread_cond_broadcast(&g_cond);
    }
    if(sig == 10)
    {
        isRuning = true;
    }
}

int main()
{
    // 作为向消息队列中添加数据的函数
    signal(15,inCache);
    pthread_t pid1,pid2;
    pthread_create(&pid1,NULL,outCache,NULL);
    pthread_create(&pid2,NULL,outCache,NULL);

    pthread_join(pid1,NULL);
    pthread_join(pid2,NULL);
    return 0;
}

4、成员函数指针使用

#include <iostream>
using namespace std;

class Test
{
public:
    Test();
    ~Test(){}
    void func(int a,int b)
    {
        cout<<"Test:"<<a<<endl;
        cout<<"Test:"<<b<<endl;
    }

    void func1(int a,int b)
    {
        cout<<"Test:"<<a<<endl;
        cout<<"Test:"<<b<<endl;
    }
};

// 函数指针
typedef void (Test::*handler)(int a,int b);

const handler handArray[] =
{
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    &Test::func1,
    &Test::func,
};

Test::Test()
{
    (this->*handArray[5])(1,2);
}

int main()
{
    Test t;
    (t.*handArray[6])(3,5);
   return 0;
}

makefile

g++ -o main pthreadPoolText.cpp

5、线程池

线程池概率:提前创建多个线程,并通过一个类来统一管理这一堆线程。
工作流程:来了一个任务,从线程池中找一个空闲的线程去处理这个任务,做完任务,循环回来等待新任务,等待新任务,

由pthreadPool.h、pthreadPool.cpp两个文件组成。
1、createPthread函数:创建线程全部线程,并将每个线程结构,放入容器中,函数中的goto语句部分,是为了保证所有线程运行起来,并且都处于pthread_cond_wait未激发状态等待。
2、call函数:中pthread_cond_broadcast唤醒一个或者多个线程,并且记录当前工作线程是否够用,每过十秒钟打印一下信息。
3、stopAll函数:唤醒一个或多个线程,并且释放资源
4、inMsgRecvQueueAndSignal函数:将消息插入消息队列中,并调用call函数。

pthreadPool.h

#ifndef __PTHREADPOOL_H_
#define __PTHREADPOOL_H_
#include <vector>
#include <atomic>
#include <pthread.h>
#include <iostream>
#include <list>
#include <unistd.h>
using namespace std;

struct student
{
    char name[256];
    unsigned int age;
    int id;
};

class pthreadPool
{
public:
    pthreadPool();
    ~pthreadPool();

public:
    bool createPthread(int threadNUm = 5);
    void stopAll();
    void call();
    void inMsgRecvQueueAndSignal(char* buf);

private:
    static void* threadFunc(void* threadData);
    void clearMsgRecvQueue();
    void msgDispose(char* jobbuf);
private:
    struct pthreadItem
    {
        bool isruning;
        pthreadPool* _pThis;
        pthread_t _Handle;
        pthreadItem(pthreadPool* pthis):_pThis(pthis),isruning(false){}
        ~pthreadItem(){}
    };
private:
    static pthread_cond_t   m_pthreadCond;      // 条件变量
    static pthread_mutex_t  m_pthreadMutex;     // 互斥量
    static bool             m_shutdown;         // 线程退出标识
    
    int                     m_iThreadNum;       // 要创建的线程数
    time_t                  m_iLastTime;        // 上次线程不够用,时间记录
    atomic<int>             m_iRunThreadNum;    // 正在运行线程数量 原子操作

    vector<pthreadItem*>    m_vThread;          // 线程容器
    list<char*>             m_msgRecvQueue;     // 消息队列
    int                     m_iRecvQueueCount;  // 收消息队列大小
};

#endif // !__PTHREADPOOL_

pthreadPool.cpp文件

#include "pthreadPool.h"

pthread_cond_t pthreadPool::m_pthreadCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t pthreadPool::m_pthreadMutex = PTHREAD_MUTEX_INITIALIZER;
bool pthreadPool::m_shutdown = false;

pthreadPool::pthreadPool()
{
    // 运行的线程数为0,时间为0,消息数0
    m_iRunThreadNum = 0;
    m_iLastTime = 0;
    m_iRecvQueueCount = 0;
}

pthreadPool::~pthreadPool()
{
    clearMsgRecvQueue();
}

bool pthreadPool::createPthread(int threadNum)
{
    if(!threadNum) return false;
    m_iThreadNum =  threadNum;
    
    pthreadItem* item;
    int errCode = 0;
    for(int i=0;i<threadNum;i++){
        item = new pthreadItem(this);
        errCode = pthread_create(&(item->_Handle),NULL,pthreadPool::threadFunc,item);
        if(errCode!=0){
            cout<<"线程创建失败:"<<i<<endl;
            return false;
        }
        m_vThread.push_back(item);
    }

    vector<pthreadItem*>::iterator iter;
lblfor:
    // goto语句作用,为了所有线程都正常启动,并且卡在pthread_cond_wait()这
    for(iter = m_vThread.begin();iter!=m_vThread.end();iter++){
        if((*iter)->isruning == false){
            usleep(100*1000);   // 单位是微秒,100毫秒
            goto lblfor;
        }
    }
    return true;
}

void* pthreadPool::threadFunc(void* threadData)
{
    pthreadItem* pThread = (pthreadItem*)threadData;
    pthreadPool* pPoll = pThread->_pThis;
    int errCode = 0;
    pthread_t tid = pthread_self();
    while(true){
        // 拿锁
        errCode = pthread_mutex_lock(&m_pthreadMutex);
        if(errCode!=0){
            cout<<"pthread_mutex_lock fail threadFunc errCode"<<errCode<<endl;
            return (void*)0;
        }

        while((pPoll->m_msgRecvQueue.size() == 0) && m_shutdown == false){
            if(pThread->isruning == false)
                pThread->isruning = true;
            
            // 整个程序初始化的时候,保证所有线程都卡在这里
            // 当线程走到这里,就会释放锁,处于未激发状态
            // 一旦被激发,就会去拿锁
            pthread_cond_wait(&m_pthreadCond,&m_pthreadMutex);
        }

        // 判断线程退出条件
        if(m_shutdown){
            pthread_mutex_unlock(&m_pthreadMutex);
            break;
        }

        // 走到这里可以去消息处理
        // 返回第一个元素,没有检查是否存在,走下来就说明有消息
        char* jobbuf = pPoll->m_msgRecvQueue.front();
        pPoll->m_msgRecvQueue.pop_front(); 
        // 消息队列数减1
        --pPoll->m_iRecvQueueCount;

        // 可以解锁了
        pthread_mutex_unlock(&m_pthreadMutex);
        // 能走到这里表示有消息,并且线程正在处理这个消息
        // 正在工作的线程数加1,原子操作
        ++pPoll->m_iRunThreadNum;
        // 消息处理
        //cout<<"消息处理开始:"<<tid<<endl;
        //sleep(3);
        //cout<<"消息处理结束:"<<tid<<endl;
        // 消息处理函数
        pPoll->msgDispose(jobbuf);
        // 消息处理结束
        //释放消息内存,运行线程数--
        ++pPoll->m_iRunThreadNum;
    }
    return (void*)0;
}

void pthreadPool::msgDispose(char* jobbuf)
{
    pthread_t tid = pthread_self();
    struct student* stu = (struct student*)jobbuf;

    cout<<"tid:"<<tid<<" name:"<<stu->name<<" age:"<<stu->age
    <<" id:"<<stu->id<<endl;

    if(stu!=NULL){
        delete stu;
        stu = NULL;
    }
}

void pthreadPool::call()
{
    // 唤醒一个等待该条件的线程,也可能是多个,也就是可以唤醒卡在pthread_cond_wait
    int errCode = pthread_cond_signal(&m_pthreadCond);
    if(errCode!=0){
        cout<<"call fail"<<endl;
        return;
    }
    // 如果工作线程数==开辟线程数需要扩容
    if(m_iRunThreadNum == m_iThreadNum)
    {
        time_t currentime = time(NULL);
        if(currentime-m_iLastTime >10)
        {
            m_iLastTime = currentime;
            cout<<"Call()发现线程池中当前空闲线程数量为0,需要考虑扩容"<<endl;
        }
    }
    return;
}

void pthreadPool::stopAll()
{
    if(m_shutdown)
        return;
    m_shutdown = true;
    int errCode = pthread_cond_broadcast(&m_pthreadCond);
    if(errCode!=0){
        cout<<"stopAll faile"<<endl;
        return;
    }

    // 等待所有线程结束
    vector<pthreadItem*>::iterator iter;
    for(iter=m_vThread.begin();iter!=m_vThread.end();iter++){
        pthread_join((*iter)->_Handle,NULL);
        if((*iter))
            delete *iter;
    }
    m_vThread.clear();
    pthread_cond_destroy(&m_pthreadCond);
    pthread_mutex_destroy(&m_pthreadMutex);
    cout<<"成功返回,线程池中线程全部正常退出"<<endl;
    return;
}

void pthreadPool::clearMsgRecvQueue()
{
    while(!m_msgRecvQueue.empty())
    {
        char* buf = m_msgRecvQueue.front();
        m_msgRecvQueue.pop_front();
        if(buf!=NULL){
            delete buf;
            buf = NULL;
        }
    }
}

void pthreadPool::inMsgRecvQueueAndSignal(char* buf)
{
    // 先互斥住
    int errCode = pthread_mutex_lock(&m_pthreadMutex);
    if(errCode!=0){
        cout<<"inMsgRecvQueueAndSignal faile lock"<<endl;
    }
    m_msgRecvQueue.push_back(buf);
    ++m_iRecvQueueCount;
    errCode = pthread_mutex_unlock(&m_pthreadMutex);
    if(errCode!=0){
        cout<<"inMsgRecvQueueAndSignal faile unlock"<<endl;
    }
    // 激发线程做事
    call();
    return;
}

main函数文件测试代码

#include "pthreadPool.h"

int main()
{
    pthreadPool* pool = new pthreadPool();
    pool->createPthread(6);
    for(int i=0;i<1000;i++){
        struct student* stu = new student();
        sprintf(stu->name,"name-%d",i);
        stu->age = i;
        stu->id = 1000+i;
        pool->inMsgRecvQueueAndSignal((char*)stu);
    }
    pool->stopAll();
    if(pool!=NULL){
        delete pool;
        pool = NULL;
    }
    pthread_exit(0);
}

makefile

all:pthreadPool

pthreadPool:pthreadPool.h pthreadPool.cpp pthreadPoolText.cpp
	g++ -o pthreadPool pthreadPool.cpp pthreadPoolText.cpp -pthread -std=c++11

6、主线程先退出对子线程影响

观察一下代码:发现主线程退出之后,子线程没有继续打印,也退出了。
造成原因:主线程执行return 之后调用量glibc库里面的exit函数进行清理处理之后,调用系统调用_exit函数进行进程退出,所以并非是主线程退出,导致子线程退出的。

void* func(void* data)
{
	while(true){
		cout<<"child loops"<<endl;
	}
	return NULL;
}

int main()
{	
	pthread_t pid;
	int errCode = pthread_create(&pid,NULL,func,NULL);
	sleep(1);
	cout<<"main exit"<<endl;
	return 0;
}

7、return、exit、pthread_exit区别

return返回到调用者
exit 退出当前进程
pthread_exit退出当前线程

8、进程和线程的区别

进程优缺点

进程优点:具有独立的地址空间,隔离性、稳定性比较好,是由操作系统管理,只要系统不出问题,一个进程的错误不会影响到其他进程。
进程缺点:共享资源麻烦

线程优缺点

线程优点:共享进程的资源,创建销毁,切换简单,速度快,占用内存小,CPU利用率高
线程缺点:需要程序员管理,相互影响几率大,一个线程挂掉将导致整个进程挂掉。

总结

一般需要频繁销毁喝创建,要处理大量运算数据,又要很好显示界面及时性比较高的,因为像这些消耗大量CPU,推荐是一个多线程,一般服务器对稳定性比较高的程序推荐使用多进程。

进程之间是如何通信的

可以通过管道pipe,信号量,共享内存,socket套接字,消息队列, 根据信息量大小,进行选择。

线程之间如何通信的

全局变量。或者自定义的消息通信机制,因为线程间共享进程的资源,所以没有像进程中用于数据交换的方式,它通信的主要目的是为了线程同步

多线程同步和互斥有几种方法

线程同步:互斥锁、信号量、信号量
互斥锁:拥有两种状态,lock和unlock,当互斥锁由某个线程持有时,互斥锁状态就变为lock,之后只有该线程有权利打开锁,其他想要获取互斥锁必须都阻塞,直到解锁。
信号量:信号量是一个计数器,用于控制访问有限共享资源数
条件变量:可以让线程满足特定条件才运行,必须搭配互斥锁一起使用。

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

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

相关文章

vscode开发基于Vue的健身房管理系统node.js

该系统的基本功能包括管理员、会员、教练三个角色功能模块。 对于管理员可以使用的功能模块主要有首页、个人中心&#xff0c;系统公告管理、健身常识管理&#xff0c;会员管理、教练管理、教练考勤管理、会员咨询管理、商品信息管理、团课管理、团课预约管理、论坛管理、系统管…

十三、二叉排序树

1、先看一个需求 给你一个数列 {7, 3, 10, 12, 5, 1, 9} &#xff0c;要求能够高效的完成对数据的查询和添加 2、解决方案分析 使用数组 数组未排序&#xff0c;优点&#xff1a;直接在数组尾添加&#xff0c;速度快。缺点&#xff1a;查找速度慢 数组排序&#xff0c;优点&…

如何推出 NFT 游戏并获得收益?- 综合指南

NFT Gaming - 简介 - NFT 是代表独特内容所有权的数字资产&#xff0c;例如游戏中的一件艺术品或收藏品。它们建立在区块链解决方案之上&#xff0c;允许这些数字资产的真正所有权和稀缺性。 在游戏行业&#xff0c;NFT 用于创建玩家可以拥有和交易的独一无二的游戏内物品。这允…

政企服务机构如何进行数字化转型?

对于服务于政府和企业的产业机构来说&#xff0c;将政府政策和企业发展做完美匹配结合是其根本。在这个过程中&#xff0c;政策信息的准确性、前瞻性、核心价值是服务的基础&#xff0c;只有在政策下可行性高的解决方案才能为政府吸引更多的企业入驻。 那么该类产业机构该如何…

如何理解​session、cookie、token的区别与联系?

session、cookie、token。 相信学过接口的朋友都特别熟悉了。 但是对我一个刚接触接口测试的小白来说&#xff0c;属实有点分不清楚。 下文就是我通过查阅各种资料总结出来的一点理解&#xff0c;不准确的地方还请各位指正。 &#xff08;文末送洗浴中心流程指南&#xff09…

C语言(函数和递归)

函数是完成特定任务的独立程序代码单元。 目录 一.函数 1.创建一个简单的函数 2.定义带形式参数的函数 3.使用return从函数中返回值 二.递归 一.函数 1.创建一个简单的函数 #include <stdio.h> void print(void); //函数原型 int main(){ print(); //函…

Weblogic CVE之旅之T3协议浅学习

前言 这篇文章主要是学习Weblogic CVE漏洞中的过程中&#xff0c;对其中的一种利用方式T3协议反序列化漏洞进行分析。 前置 什么是T3协议&#xff1f; T3协议是一种Weblogic RMI 调用时的通信协议, 对于JAVA RMI(Remote Method Invocation) 来说&#xff0c;基础的通信协议…

Web前端:使用Angular CLI时的最佳实践和专业技巧

在web开发业务中&#xff0c;构建高性能的应用程序是首要因素。此外&#xff0c;用开发人员最流行的语言开发一个健壮的网站将始终为构建高功能的网站提供适当的基础网站。相比之下&#xff0c;不可否认&#xff0c;Angular CLI是建立得最好且正在成长的框架之一。Angular CLI简…

【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式 国赛 程序设计试题以及详细题解

文章目录原题展示原题分析详细题解LED模块按键模块串口LCD模块模拟电压读取(ADC)脉冲输入输出文章福利原题展示 原题分析 本届国赛试题主要包含LCD、LED、按键、EEPROM、串口、模拟电压输入、脉冲输入输出七大部分&#xff0c;其中前面三个部分是蓝桥杯嵌入式的“亲儿子”(必考…

2023年妇女节是哪一天 妇女节是2023年几月几日?

2023年妇女节是哪一天是2023年几月几日&#xff1f; 2023年妇女节是2023年3月8日 三八妇女节是国家法定节假日吗&#xff1f; 妇女节不是国家法定节假日&#xff0c;而国家法定节假日包括&#xff1a;元旦、春节、清明节、劳动节、端午节、中秋节、国庆节&#xff1b; 关于三…

操作系统(day09) -- 连续分配管理方式

连续分配管理方式 单元连续分配 动态分区分配 1.系统要用什么样的数据结构记录内存的使用情况&#xff1f; 两种常用的数据结构 空闲分区表 每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息空闲分区链 每个分区的起始部分和末尾部分分别设置前向…

硬件学习 软件Cadence day02 画原理图的基本操作 (键盘快捷键 , 原理图设计流程 , 从开始到导出网表流程)

1. ORCAD Capture cls 界面的快捷键 键盘 按键对应的操作I放大 &#xff08;可以滚轮操作&#xff09;O缩小 &#xff08;可以滚轮操作&#xff09;W画线Esc退出现在的状态 &#xff08;画图界面 右键 End xxx&#xff09;N放置网络标号J放置节点 (控制…

花2个月面过华为测开岗,拿个30K不过分吧?

背景介绍 美本计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实…

ChatGPT is not all you need,一文看尽SOTA生成式AI模型:6大公司9大类别21个模型全回顾(一)

文章目录ChatGPT is not all you need&#xff0c;一文看尽SOTA生成式AI模型&#xff1a;6大公司9大类别21个模型全回顾&#xff08;一&#xff09;Text-to-Image 模型DALL-E 2IMAGENStable DiffusionMuseText-to-3D 模型DreamfusionMagic3DChatGPT is not all you need&#x…

Python和Java语言,哪个更适合做自动化测试?

经常有测试新手问我&#xff1a;Python和Java语言&#xff0c;哪个更适合做自动化测试&#xff1f;本来想简单的回答一下的&#xff0c;但又觉得对不起大家对小编的信任。因此&#xff0c;小编今天专门写了一篇文章来回答这个问题。欢迎各位大佬补充~1、什么是自动化测试&#…

大数据框架之Hadoop:HDFS(八)HDFS HA高可用

8.1 HA概述 1&#xff09;所谓HA&#xff08;High Available&#xff09;&#xff0c;即高可用&#xff08;7*24小时不中断服务&#xff09;。 2&#xff09;实现高可用最关键的策略是消除单点故障。HA严格来说应该分成各个组件的HA机制&#xff1a;HDFS的HA和YARN的HA。 3&…

一文优化java.lang.StackOverflowError的堆栈溢出问题及递归引发的java.lang.StackOverflowError错误

文章目录1. 问题引出2. 分析问题2.1 为什么递归调用会导致堆栈溢出2.2 数组太大或分配内存多于可用内存导致堆栈异常3. 优化避免栈溢出3.1 尾递归优化3.2 循环替代递归4. 重要总结1. 问题引出 今天在编码时&#xff0c;出现了java.lang.StackOverflowError&#xff0c;就感觉很…

【Python小游戏】没点儿技术真不敢这么玩儿:人工智能挑战《贪吃蛇》,来自AI的艺术——超级游戏高手世界最高纪录秒被盘?

前言 每天分享各种Python项目、好玩的Pygame游戏、Python的爬虫、数据分析案例、有趣的人 工智能知识等。期待你的关注哦&#xff01; 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 哈喽&…

探索IP地址的应用

无论是互联网行业还是传统行业都会用到网络&#xff0c;作为企业如何维护网络安全&#xff0c;保障网站不被攻击&#xff0c;数据不被泄露等。这个时候我们就会通查询IP归属地&#xff0c;辅助企业解决安全问题。下面介绍一下ip归属地在各行业的具体应用。1.网安行业应用一&…

SpringBoot实现登录拦截器超详细(springboot拦截器excludePathPatterns方法不生效的坑)

文章目录SpringBoot实现登录拦截器1、SpringBoot 实现登录拦截的原理1.1、实现HandlerInterceptor接口1.2、实现WebMvcConfigurer接口&#xff0c;注册拦截器1.3、保持登录状态springboot拦截器excludePathPatterns方法不生效的坑与解决方法一、前言二、问题三、解决方法四、总…