Linux系统编程---线程池并发服务器

模型原理分析:

线程池的关键优势在于它减少了每次任务执行时创建和销毁线程的开销

线程池的组成主要分为 3 个部分,这三部分配合工作就可以得到一个完整的线程池: 

1. 任务队列,存储需要处理的任务,由工作的线程来处理这些任务

        通过线程池提供的 API 函数,将一个待处理的任务(客户端提交)添加到任务队列,或者从任务队列中删除,已处理的任务会从任务队列中删除;线程池的使用者,也就是调用线程池函数往任务队列中添加任务的线程即生产者线程

2. 工作的线程(任务队列任务的消费者) ,N个

        线程池中维护了一定数量的工作线程,他们的作用是是不停的读任务队列,从里边取出任务并处理;工作的线程相当于是任务队列的消费者角色;如果任务队列为空,工作的线程将会被阻塞 (使用条件变量 / 信号量阻塞);如果阻塞之后有了新的任务,由生产者将阻塞解除,工作线程开始工作

3. 管理者线程(不处理任务队列中的任务),1个

        它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测。当任务过多的时候,可以适当的创建一些新的工作线程。当任务过少的时候,可以适当的销毁一些工作的线程

线程池的工作原理的基本步骤: 

1. 初始化:

        在程序开始时或者当需要使用多线程时,创建一定数量的线程,这些线程通常会在一个阻塞状态下等待任务的分配。这些线程被创建并加入到池中,形成一个可用线程的集合。

2. 等待任务:

        线程池中的线程通常会等待任务队列,直到有任务提交给线程池执行。

3. 任务的分配:

        当一个外部线程(线程池的客户端)提交一个任务时,任务会被添加到任务队列中。线程池中的某个线程(通常是队列中的第一个线程)会从队列中取出任务并执行。

4. 执行任务:

        被分配任务的线程会执行具体的任务,在任务执行期间,该线程不会对其他任务可用。

5. 任务完成:

        一旦任务完成,线程会报告任务完成,并且变得再次可用于执行队列中的下一个任务,如果任务执行过程中产生了异常,异常会被捕获并根据具体的线程池实现处理。

6. 线程的循环使用:

        完成任务的线程会返回到线程池中,处于等待状态,直到再次被分配新的任务

7. 关闭线程池:

        当不再需要线程池时,可以关闭线程池。这通常涉及到发送中断信号给所有等待的线程,停止它们等待新的任务。有些线程池会等待所有正在执行的任务完成后再终止线程。一旦线程池关闭,它就不能再接收新的任务了。

线程池模块分析:

1. main();        

        创建线程池。

        向线程池中添加任务。 借助回调处理任务。

        销毁线程池。

2. pthreadpool_create();

        创建线程池结构体 指针。

        初始化线程池结构体 {  N 个成员变量 }

        创建 N 个任务线程。

        创建 1 个管理者线程。

        失败时,销毁开辟的所有空间。(释放)

3. threadpool_thread()

        进入子线程回调函数。

        接收参数 void *arg  --> pool 结构体

        加锁 -->lock --> 整个结构体锁

        判断条件变量 --> wait  -------------------170

4. adjust_thread()

        循环 10 s 执行一次。

        进入管理者线程回调函数

        接收参数 void *arg  --> pool 结构体

        加锁 -->lock --> 整个结构体锁

        获取管理线程池要用的到 变量。    task_num, live_num, busy_num

        根据既定算法,使用上述3变量,判断是否应该 创建、销毁线程池中 指定步长的线程。

5. threadpool_add ()

        总功能:

            模拟产生任务。   num[20]

            设置回调函数, 处理任务。  sleep(1) 代表处理完成。

        内部实现:
            加锁

            初始化 任务队列结构体成员。   回调函数 function, arg

            利用环形队列机制,实现添加任务。 借助队尾指针挪移 % 实现。

            唤醒阻塞在 条件变量上的线程。
            解锁

6.  从 3. 中的wait之后继续执行,处理任务。

        加锁   
        获取 任务处理回调函数,及参数

        利用环形队列机制,实现处理任务。 借助队头指针挪移 % 实现。

        唤醒阻塞在 条件变量 上的 server。

        解锁

        加锁 

        改忙线程数++

        解锁

        执行处理任务的线程

        加锁 

        改忙线程数--

        解锁

7. 创建 销毁线程

        管理者线程根据 task_num, live_num, busy_num  

        根据既定算法,使用上述3变量,判断是否应该 创建、销毁线程池中 指定步长的线程。

        如果满足 创建条件

            pthread_create();   回调 任务线程函数。        live_num++

        如果满足 销毁条件

            wait_exit_thr_num = 10;  

            signal 给 阻塞在条件变量上的线程 发送 假条件满足信号    

            跳转至  --170 wait阻塞线程会被 假信号 唤醒。判断: wait_exit_thr_num  > 0 pthread_exit();      

C代码实现

头文件声明threadpool.h

#ifndef __THREADPOOL_H_
#define __THREADPOOL_H_

typedef struct threadpool_t threadpool_t;

threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);

int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg);

int threadpool_destroy(threadpool_t *pool);

int threadpool_all_threadnum(threadpool_t *pool);

int threadpool_busy_threadnum(threadpool_t *pool);

#endif

源文件 threadpool.c:

#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include "threadpool.h"

#define DEFAULT_TIME 10                 /*10s检测一次*/
#define MIN_WAIT_TASK_NUM 10            /*如果queue_size > MIN_WAIT_TASK_NUM 添加新的线程到线程池*/ 
#define DEFAULT_THREAD_VARY 10          /*每次创建和销毁线程的个数*/
#define true 1
#define false 0

typedef struct {
    void *(*function)(void *);          /* 函数指针,回调函数 */
    void *arg;                          /* 上面函数的参数 */
} threadpool_task_t;                    /* 各子线程任务结构体 */

/* 描述线程池相关信息 */

struct threadpool_t {
    pthread_mutex_t lock;               /* 用于锁住本结构体 */    
    pthread_mutex_t thread_counter;     /* 记录忙状态线程个数de琐 -- busy_thr_num */

    pthread_cond_t queue_not_full;      /* 当任务队列满时,添加任务的线程阻塞,等待此条件变量 */
    pthread_cond_t queue_not_empty;     /* 任务队列里不为空时,通知等待任务的线程 */

    pthread_t *threads;                 /* 存放线程池中每个线程的tid。数组 */
    pthread_t adjust_tid;               /* 存管理线程tid */
    threadpool_task_t *task_queue;      /* 任务队列(数组首地址) */

    int min_thr_num;                    /* 线程池最小线程数 */
    int max_thr_num;                    /* 线程池最大线程数 */
    int live_thr_num;                   /* 当前存活线程个数 */
    int busy_thr_num;                   /* 忙状态线程个数 */
    int wait_exit_thr_num;              /* 要销毁的线程个数 */

    int queue_front;                    /* task_queue队头下标 */
    int queue_rear;                     /* task_queue队尾下标 */
    int queue_size;                     /* task_queue队中实际任务数 */
    int queue_max_size;                 /* task_queue队列可容纳任务数上限 */

    int shutdown;                       /* 标志位,线程池使用状态,true或false */
};

void *threadpool_thread(void *threadpool);

void *adjust_thread(void *threadpool);

int is_thread_alive(pthread_t tid);
int threadpool_free(threadpool_t *pool);

//threadpool_create(3,100,100);  
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
    int i;
    threadpool_t *pool = NULL;          /* 线程池 结构体 */

    do {
        if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) {  
            printf("malloc threadpool fail");
            break;                                      /*跳出do while*/
        }

        pool->min_thr_num = min_thr_num;
        pool->max_thr_num = max_thr_num;
        pool->busy_thr_num = 0;
        pool->live_thr_num = min_thr_num;               /* 活着的线程数 初值=最小线程数 */
        pool->wait_exit_thr_num = 0;
        pool->queue_size = 0;                           /* 有0个产品 */
        pool->queue_max_size = queue_max_size;          /* 最大任务队列数 */
        pool->queue_front = 0;
        pool->queue_rear = 0;
        pool->shutdown = false;                         /* 不关闭线程池 */

        /* 根据最大线程上限数, 给工作线程数组开辟空间, 并清零 */
        pool->threads = (pthread_t *)malloc(sizeof(pthread_t)*max_thr_num); 
        if (pool->threads == NULL) {
            printf("malloc threads fail");
            break;
        }
        memset(pool->threads, 0, sizeof(pthread_t)*max_thr_num);

        /* 给 任务队列 开辟空间 */
        pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t)*queue_max_size);
        if (pool->task_queue == NULL) {
            printf("malloc task_queue fail");
            break;
        }

        /* 初始化互斥琐、条件变量 */
        if (pthread_mutex_init(&(pool->lock), NULL) != 0
                || pthread_mutex_init(&(pool->thread_counter), NULL) != 0
                || pthread_cond_init(&(pool->queue_not_empty), NULL) != 0
                || pthread_cond_init(&(pool->queue_not_full), NULL) != 0)
        {
            printf("init the lock or cond fail");
            break;
        }

        /* 启动 min_thr_num 个 work thread */
        for (i = 0; i < min_thr_num; i++) {
            pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);   /*pool指向当前线程池*/
            printf("start thread 0x%x...\n", (unsigned int)pool->threads[i]);
        }
        pthread_create(&(pool->adjust_tid), NULL, adjust_thread, (void *)pool);     /* 创建管理者线程 */

        return pool;

    } while (0);

    threadpool_free(pool);      /* 前面代码调用失败时,释放poll存储空间 */

    return NULL;
}

/* 向线程池中 添加一个任务 */
//threadpool_add(thp, process, (void*)&num[i]);   /* 向线程池中添加任务 process: 小写---->大写*/

int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg)
{
    pthread_mutex_lock(&(pool->lock));

    /* ==为真,队列已经满, 调wait阻塞 */
    while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown)) {
        pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
    }

    if (pool->shutdown) {
        pthread_cond_broadcast(&(pool->queue_not_empty));
        pthread_mutex_unlock(&(pool->lock));
        return 0;
    }

    /* 清空 工作线程 调用的回调函数 的参数arg */
    if (pool->task_queue[pool->queue_rear].arg != NULL) {
        pool->task_queue[pool->queue_rear].arg = NULL;
    }

    /*添加任务到任务队列里*/
    pool->task_queue[pool->queue_rear].function = function;
    pool->task_queue[pool->queue_rear].arg = arg;
    pool->queue_rear = (pool->queue_rear + 1) % pool->queue_max_size;       /* 队尾指针移动, 模拟环形 */
    pool->queue_size++;

    /*添加完任务后,队列不为空,唤醒线程池中 等待处理任务的线程*/
    pthread_cond_signal(&(pool->queue_not_empty));
    pthread_mutex_unlock(&(pool->lock));

    return 0;
}

/* 线程池中各个工作线程 */
void *threadpool_thread(void *threadpool)
{
    threadpool_t *pool = (threadpool_t *)threadpool;
    threadpool_task_t task;

    while (true) {
        /* Lock must be taken to wait on conditional variable */
        /*刚创建出线程,等待任务队列里有任务,否则阻塞等待任务队列里有任务后再唤醒接收任务*/
        pthread_mutex_lock(&(pool->lock));

        /*queue_size == 0 说明没有任务,调 wait 阻塞在条件变量上, 若有任务,跳过该while*/
        while ((pool->queue_size == 0) && (!pool->shutdown)) {  
            printf("thread 0x%x is waiting\n", (unsigned int)pthread_self());
            pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));

            /*清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程*/
            if (pool->wait_exit_thr_num > 0) {
                pool->wait_exit_thr_num--;

                /*如果线程池里线程个数大于最小值时可以结束当前线程*/
                if (pool->live_thr_num > pool->min_thr_num) {
                    printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
                    pool->live_thr_num--;
                    pthread_mutex_unlock(&(pool->lock));

                    pthread_exit(NULL);
                }
            }
        }

        /*如果指定了true,要关闭线程池里的每个线程,自行退出处理---销毁线程池*/
        if (pool->shutdown) {
            pthread_mutex_unlock(&(pool->lock));
            printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
            pthread_detach(pthread_self());
            pthread_exit(NULL);     /* 线程自行结束 */
        }

        /*从任务队列里获取任务, 是一个出队操作*/
        task.function = pool->task_queue[pool->queue_front].function;
        task.arg = pool->task_queue[pool->queue_front].arg;

        pool->queue_front = (pool->queue_front + 1) % pool->queue_max_size;       /* 出队,模拟环形队列 */
        pool->queue_size--;

        /*通知可以有新的任务添加进来*/
        pthread_cond_broadcast(&(pool->queue_not_full));

        /*任务取出后,立即将 线程池琐 释放*/
        pthread_mutex_unlock(&(pool->lock));

        /*执行任务*/ 
        printf("thread 0x%x start working\n", (unsigned int)pthread_self());
        pthread_mutex_lock(&(pool->thread_counter));                            /*忙状态线程数变量琐*/
        pool->busy_thr_num++;                                                   /*忙状态线程数+1*/
        pthread_mutex_unlock(&(pool->thread_counter));

        (*(task.function))(task.arg);                                           /*执行回调函数任务*/
        //task.function(task.arg);                                              /*执行回调函数任务*/

        /*任务结束处理*/ 
        printf("thread 0x%x end working\n", (unsigned int)pthread_self());
        pthread_mutex_lock(&(pool->thread_counter));
        pool->busy_thr_num--;                                       /*处理掉一个任务,忙状态数线程数-1*/
        pthread_mutex_unlock(&(pool->thread_counter));
    }

    pthread_exit(NULL);
}

/* 管理线程 */
void *adjust_thread(void *threadpool)
{
    int i;
    threadpool_t *pool = (threadpool_t *)threadpool;
    while (!pool->shutdown) {

        sleep(DEFAULT_TIME);                                    /*定时 对线程池管理*/

        pthread_mutex_lock(&(pool->lock));
        int queue_size = pool->queue_size;                      /* 关注 任务数 */
        int live_thr_num = pool->live_thr_num;                  /* 存活 线程数 */
        pthread_mutex_unlock(&(pool->lock));

        pthread_mutex_lock(&(pool->thread_counter));
        int busy_thr_num = pool->busy_thr_num;                  /* 忙着的线程数 */
        pthread_mutex_unlock(&(pool->thread_counter));

        /* 创建新线程 算法: 任务数大于最小线程池个数, 且存活的线程数少于最大线程个数时 如:30>=10 && 40<100*/
        if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num) {
            pthread_mutex_lock(&(pool->lock));  
            int add = 0;

            /*一次增加 DEFAULT_THREAD 个线程*/
            for (i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY
                    && pool->live_thr_num < pool->max_thr_num; i++) {
                if (pool->threads[i] == 0 || !is_thread_alive(pool->threads[i])) {
                    pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
                    add++;
                    pool->live_thr_num++;
                }
            }

            pthread_mutex_unlock(&(pool->lock));
        }

        /* 销毁多余的空闲线程 算法:忙线程X2 小于 存活的线程数 且 存活的线程数 大于 最小线程数时*/
        if ((busy_thr_num * 2) < live_thr_num  &&  live_thr_num > pool->min_thr_num) {

            /* 一次销毁DEFAULT_THREAD个线程, 隨機10個即可 */
            pthread_mutex_lock(&(pool->lock));
            pool->wait_exit_thr_num = DEFAULT_THREAD_VARY;      /* 要销毁的线程数 设置为10 */
            pthread_mutex_unlock(&(pool->lock));

            for (i = 0; i < DEFAULT_THREAD_VARY; i++) {
                /* 通知处在空闲状态的线程, 他们会自行终止*/
                pthread_cond_signal(&(pool->queue_not_empty));
            }
        }
    }

    return NULL;
}

int threadpool_destroy(threadpool_t *pool)
{
    int i;
    if (pool == NULL) {
        return -1;
    }
    pool->shutdown = true;

    /*先销毁管理线程*/
    pthread_join(pool->adjust_tid, NULL);

    for (i = 0; i < pool->live_thr_num; i++) {
        /*通知所有的空闲线程*/
        pthread_cond_broadcast(&(pool->queue_not_empty));
    }
    for (i = 0; i < pool->live_thr_num; i++) {
        pthread_join(pool->threads[i], NULL);
    }
    threadpool_free(pool);

    return 0;
}

int threadpool_free(threadpool_t *pool)
{
    if (pool == NULL) {
        return -1;
    }

    if (pool->task_queue) {
        free(pool->task_queue);
    }
    if (pool->threads) {
        free(pool->threads);
        pthread_mutex_lock(&(pool->lock));
        pthread_mutex_destroy(&(pool->lock));
        pthread_mutex_lock(&(pool->thread_counter));
        pthread_mutex_destroy(&(pool->thread_counter));
        pthread_cond_destroy(&(pool->queue_not_empty));
        pthread_cond_destroy(&(pool->queue_not_full));
    }
    free(pool);
    pool = NULL;

    return 0;
}

int threadpool_all_threadnum(threadpool_t *pool)
{
    int all_threadnum = -1;                 // 总线程数

    pthread_mutex_lock(&(pool->lock));
    all_threadnum = pool->live_thr_num;     // 存活线程数
    pthread_mutex_unlock(&(pool->lock));

    return all_threadnum;
}

int threadpool_busy_threadnum(threadpool_t *pool)
{
    int busy_threadnum = -1;                // 忙线程数

    pthread_mutex_lock(&(pool->thread_counter));
    busy_threadnum = pool->busy_thr_num;    
    pthread_mutex_unlock(&(pool->thread_counter));

    return busy_threadnum;
}

int is_thread_alive(pthread_t tid)
{
    int kill_rc = pthread_kill(tid, 0);     //发0号信号,测试线程是否存活
    if (kill_rc == ESRCH) {
        return false;
    }
    return true;
}

/*测试*/ 

#if 1

/* 线程池中的线程,模拟处理业务 */
void *process(void *arg)
{
    printf("thread 0x%x working on task %d\n ",(unsigned int)pthread_self(),(int)arg);
    sleep(1);                           //模拟 小---大写
    printf("task %d is end\n",(int)arg);

    return NULL;
}

int main(void)
{
    /*threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);*/

    threadpool_t *thp = threadpool_create(3,100,100);   /*创建线程池,池里最小3个线程,最大100,队列最大100*/
    printf("pool inited");

    //int *num = (int *)malloc(sizeof(int)*20);
    int num[20], i;
    for (i = 0; i < 20; i++) {
        num[i] = i;
        printf("add task %d\n",i);
        
        /*int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg) */

        threadpool_add(thp, process, (void*)&num[i]);   /* 向线程池中添加任务 */
    }

    sleep(10);                                          /* 等子线程完成任务 */
    threadpool_destroy(thp);

    return 0;
}

#endif

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux网络服务-DHCP

一、DHCP工作原理 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;&#xff1a;用于自动获取IP地址 1.客户端会发送一个广播DHCP Discover报文去寻找DHCP服务器 2.客户端只会接收第一个回复的DHCP服务器的报文 3.服务器会发…

MATLAB非均匀网格梯度计算

在matlab中&#xff0c;gradient函数可以很方便的对均匀网格进行梯度计算&#xff0c;但是对于非均匀网格&#xff0c;但是gradient却无法求解非均匀网格的梯度&#xff0c;这一点我之前犯过错误。我之前以为在gradient函数中指定x&#xff0c;y等坐标&#xff0c;其求解的就是…

Python异步Redis客户端与通用缓存装饰器

前言 这里我将通过 redis-py 简易封装一个异步的Redis客户端&#xff0c;然后主要讲解设计一个支持各种缓存代理&#xff08;本地内存、Redis等&#xff09;的缓存装饰器&#xff0c;用于在减少一些不必要的计算、存储层的查询、网络IO等。 具体代码都封装在 HuiDBK/py-tools: …

Ubuntu TeamViewer安装与使用

TeamViewer是一款跨平台的专有应用程序&#xff0c;允许用户通过互联网连接从全球任何地方远程连接到工作站、传输文件以及召开在线会议。它适用于多种设备&#xff0c;例如个人电脑、智能手机和平板电脑。 TeamViewer可以派上用场&#xff0c;尤其是在排除交通不便或偏远地区…

Open SUSE 安装MySQL

前言 看了一圈网上关于SUSE的教程实在是太少了&#xff0c;毕竟太小众了。这两天在安装MySQL的时候老是出问题&#xff0c;踩了一晚上的坑&#xff0c;发现其实很简单&#xff0c;网上看了方法大概有这几种 通过Yast software management安装&#xff0c;但是我尝试了&#x…

Linux下的基本指令(1)

嗨喽大家好呀&#xff01;今天阿鑫给大家带来Linux下的基本指令&#xff08;1&#xff09;&#xff0c;下面让我们一起进入Linux的学习吧&#xff01; Linux下的基本指令 ls 指令pwd命令cd 指令touch指令mkdir指令(重要)rmdir指令 && rm 指令(重要)man指令(重要)cp指…

海外三大AI图片生成器对比(Stable Diffusion、Midjourney、DALL·E 3)

Stable Diffusion DreamStudio 是Stable Diffusion 的官方网页&#xff0c;价格便宜&#xff0c;对图片的操作性强&#xff0c;但同时编辑页面不太直观&#xff0c;对使用者的要求较高。 与 DALLE 和 Midjourney 不同&#xff0c;Stable Diffusion 是开源的。这也意味着&…

[图解]领域驱动设计伪创新-为什么互联网是重灾区-02

0 00:00:00,000 --> 00:00:04,737 我们并没有说&#xff0c;微信或者是美团用了领域驱动设计 1 00:00:04,737 --> 00:00:06,632 没有做这个暗示 2 00:00:06,632 --> 00:00:08,290 我只是说什么 3 00:00:09,350 --> 00:00:12,700 针对用户量很大的系统 4 00:00:…

CANoe如何实现TLS协议

TLS&#xff0c;Transport Layer Security&#xff0c;传输层安全协议。是在传输层和应用层之间&#xff0c;为了保证应用层数据能够安全可靠地通过传输层传输且不会泄露的安全防护。 TLS安全协议的实现逻辑&#xff0c;在作者本人看来&#xff0c;大致分为三个部分&#xff1…

minio主从同步和双机热备

文章目录 1. 安装2. 测试3. 双机热备 环境说明 服务器IPminio-slb10.10.xxx.251minio-01/02/03/0410.10.xxx.25/206/207/208minio-backup10.10.xxx.204 1. 安装 下载地址&#xff1a; http://dl.minio.org.cn/client/mc/release/linux-amd64/mc 安装 只有一个二进制文件&…

Spring Security OAuth2 统一登录

介绍 Spring Security OAuth2 是一个在 Spring Security 框架基础上构建的 OAuth2 授权服务器和资源服务器的扩展库。它提供了一套功能强大的工具和组件&#xff0c;用于实现 OAuth2 协议中的授权流程、令牌管理和访问控制。 Git地址&#xff1a;yunfeng-boot3-sercurity: Sp…

贴片OB2500POPA OB2500POP SOP-8 电源开关控制器IC芯片

OB2500POPA电源管理芯片被广泛应用于各种低功耗AC-DC充电器和适配器中。以下是该芯片的一些典型应用案例&#xff1a; 手机充电器&#xff1a;OB2500POPA可以用于设计高效、小巧的手机充电器&#xff0c;提供稳定的输出电压和电流。 USB充电器&#xff1a;在USB充电器中&…

第5篇:创建Nios II工程之Hello_World<四>

Q&#xff1a;最后我们在DE2-115开发板上演示运行Hello_World程序。 A&#xff1a;先烧录编译Quartus硬件工程时生成的.sof文件&#xff0c;在FPGA上成功配置Nios II系统&#xff1b;然后在Nios II Eclipse窗口右键点击工程名hello_world&#xff0c;选择Run As-->Nios II …

Node.js 版本升级方法

在构建vue项目时&#xff0c;依赖npm&#xff08;Node Package Manager&#xff09;工具&#xff0c;类似于Java项目需要maven管理。而npm是node.js的管理工具&#xff0c;npm依赖node.js环境才能执行。 有时候使用voscode或者其他工具安装vue项目依赖&#xff0c;显示一直处于…

Apollo共创生态:共筑未来智能出行新篇章

目录 引言Apollo七周年大会回顾心路历程企业生态计划 个人心得与启发技术革新的引领者展望 结语 引言 在科技飞速发展的今天&#xff0c;智能出行已经成为全球关注的焦点。Apollo开放平台&#xff0c;作为智能出行领域的先行者&#xff0c;已经走过了七个春秋。七年磨一剑&…

vue3+antv+ts实现勾选同意协议复选框之后才能继续注册登录

效果如下&#xff1a; 勾选复选框之前 勾选复选框之后 这里偷懒了&#xff0c;没有把登录和注册按钮分开控制&#xff0c;自己实操的时候可以去细化一下功能 代码如下&#xff1a; <script setup lang"ts"> import { ref, defineProps, reactive } from &qu…

【Spring Boot 源码学习】SpringApplication 的 run 方法监听器

《Spring Boot 源码学习系列》 SpringApplication 的 run 方法监听器 一、引言二、主要内容2.1 SpringApplicationRunListeners2.2 SpringApplicationRunListener2.3 实现类 EventPublishingRunListener2.3.1 成员变量和构造方法2.3.2 成员方法2.3.2.1 不同阶段的事件处理2.3.2…

分享一些常用的内外网文件传输工具

内外网隔离后的文件传输是网络安全领域中一个常见而又重要的问题。随着信息技术的快速发展&#xff0c;网络安全问题日益凸显&#xff0c;内外网隔离成为了许多企业和组织保护内部信息安全的重要手段。然而&#xff0c;内外网隔离后如何有效地进行文件传输&#xff0c;成为了摆…

Redis__数据类型

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a;Redis__数据类型 ⏱️ 创作时间&#xff1a;2024年04月28日 ———————————————— 这里写目录标题 文…

Virtualbox7.0.10--创建虚拟机

前言 下载Virtualbox7.0.10&#xff0c;可参考《Virtualbox–下载指定版本》 Virtualbox7.0.10具体安装步骤&#xff0c;可参考《Virtualbox7.0.10的安装步骤》 创建虚拟机 1.双击打开Virtualbox 后&#xff0c;单击“新建”&#xff0c;进入新建虚拟电脑页面 2. 设置虚拟电脑…
最新文章