线程池的实现全过程v1.0版本(手把手创建,看完必掌握!!!)

news2024/11/15 14:05:42

目录

线程池的实现过程

线程池的创建

添加任务队列

线程进行处理任务

线程池资源释放

线程池完整程序

线程池v1.0版本总结


线程池的实现过程

实现线程池首先要确定线程池有哪些属性

  • 线程池中线程的数量
  • 线程池中已工作的线程数量
  • 任务队列
  • 任务队列的大小
  • 任务队列的锁

还需要确定线程池的三种条件情况

(1)任务队列为空时:线程池里的线程需要阻塞等待

(2)任务队列为满:不能再新增任务

(3)任务队列不为空:线程池里的线程处理任务

线程池的创建

定义任务队列结构体

所有任务以函数指针的方式存储。

struct job
{
    void *(*func)(void *arg);
    void *arg;
    struct job *next;
};

定义线程池结构体

struct threadpool
{
    int thread_num;         //已开启的线程数量
    pthread_t *pthread_ids; //保存线程池中线程的id

    struct job *head;  //任务队列的头
    struct job *tail;  //任务队列的尾
    int queue_max_num; //任务队列的最大数
    int queue_cur_num; //任务队列已有多少个任务

    pthread_mutex_t mutex;
    pthread_cond_t queue_empty;     //控制任务队列为空的条件
    pthread_cond_t queue_not_empty; //控制任务队列不为空的条件
    pthread_cond_t queue_not_full;  //控制任务队列不为满的条件
};

在main函数中,创建一个线程池,并初始化线程池中线程数量和任务数量,最后分配线程号

    struct threadpool *pool = (struct threadpool*)malloc(sizeof(struct threadpool));
    //malloc
    int thread_num = 20;
    int queue_max_num = 100;
    
    pool->thread_num = thread_num;
    pool->pthread_ids = malloc(sizeof(pthread_t)*thread_num);
    //malloc

接着创建线程池中线程,注意要将线程池作为参数传入到线程处理函数中,让每个线程知道当前属于哪个线程池,还要知道当前处理的是哪个任务队列(任务队列的头和尾刚好存储在线程池中)

注:也可以将struct threadpool *pool = (struct threadpool*)malloc(sizeof(struct threadpool));放在全局范围内,这样每个线程都可以看到,但是不安全。因此我们定义为局部变量。

    void* threadpool_function(void *arg)
    {
        struct threadpool *pool = (struct threadpool*)arg;
        while (1)
        {
           
        }
    }
    ...
    int main()
    ...
    for(int i = 0;i<pool->thread_num;i++)
    {
        pthread_create(&(pool->pthread_ids[i]),NULL,threadpool_function,(void*)pool);
    }
    ...

初始化任务队列

    pool->queue_max_num = queue_max_num;
    pool->queue_cur_num=0;
    pool->head=NULL;
    pool->tail=NULL;

最后初始化锁和条件变量

    pthread_mutex_init(&(pool->mutex),NULL);
    pthread_cond_init(&(pool->queue_empty),NULL);
    pthread_cond_init(&(pool->queue_not_empty),NULL);
    pthread_cond_init(&(pool->queue_not_full),NULL);

对于上面线程池的创建我们可以封装成一个函数

struct threadpool* threadpool_init(int thread_num,int queue_max_num)
{
    struct threadpool *pool = (struct threadpool*)malloc(sizeof(struct threadpool));
    //malloc
    pool->queue_max_num = queue_max_num;
    pool->queue_cur_num=0;
    pool->head=NULL;
    pool->tail=NULL;

    pthread_mutex_init(&(pool->mutex),NULL);
    pthread_cond_init(&(pool->queue_empty),NULL);
    pthread_cond_init(&(pool->queue_not_empty),NULL);
    pthread_cond_init(&(pool->queue_not_full),NULL);
    
    pool->thread_num = thread_num;
    pool->pthread_ids = malloc(sizeof(pthread_t)*thread_num);
    //malloc

    for(int i = 0;i<pool->thread_num;i++)
    {
        pthread_create(&(pool->pthread_ids[i]),NULL,threadpool_function,(void*)pool);
    }
    return pool;
}

我们一个要实现三个接口:创建好线程池之后,线程池需要去处理任务,我们还需要向任务队列里面添加任务。

线程池怎么用,核心就是实现线程池里面的线程处理函数,再一个就是往任务队列里添加任务。

添加任务队列

线程如何工作需要依托于任务队列里的任务来做,因此我们先不继续写线程处理函数,我们先往任务队列里存放任务。

任务队列里每一个节点放的是一个函数指针,我们将想要每个线程做的操作封装成一个函数,然后把函数的地址传给这个指针。这样的话,每一个节点函数指针对应的就是所要执行的任务。

我们让每个任务都打印两句话

void *work(void *arg)
{
    char *p = (char *)arg;
    printf("hello world! %s\n", p);
    printf("welcome to Nanjing! %s\n", p);
    sleep(1);
}

我们将向任务队列里添加一个任务的操作也封装成函数

void threadpool_add_job(struct threadpool *pool, void *(*func)(void *arg), void *arg)
{
    struct job *pjob = (struct job *)malloc(sizeof(struct job));

    pjob->func = func;
    pjob->arg = arg;

    if (pool->head == NULL)
    {
        pool->head = pool->tail = pjob;
    }
    else
    {
        pool->tail->next = pjob;
        pool->tail = pjob;
    }
    pool->queue_cur_num++;
}

上面只是最基本的添加任务,我们还需要对任务队列进行状态判断

对任务队列已满的情况添加进来,将上锁解锁和条件变量用上

void threadpool_add_job(struct threadpool *pool, void *(*func)(void *arg), void *arg)
{
    pthread_mutex_lock(&(pool->mutex));
    //如果任务队列已满
    while(pool->queue_cur_num == pool->queue_max_num)
    {
        pthread_cond_wait(&(pool->queue_not_full),&(pool->mutex));
    }
    struct job *pjob = (struct job *)malloc(sizeof(struct job));

    pjob->func = func;
    pjob->arg = arg;

    if (pool->head == NULL)
    {
        pool->head = pool->tail = pjob;
    }
    else
    {
        pool->tail->next = pjob;
        pool->tail = pjob;
    }
    pool->queue_cur_num++;
    pthread_mutex_unlock(&(pool->mutex));
}

线程进行处理任务

在线程处理函数中对任务队列进行任务处理,线程的工作方式是每次从任务队列取一个任务去执行操作,执行完成之后回头来再取,来回往复

void *threadpool_function(void *arg)
{
    struct threadpool *pool = (struct threadpool *)arg;
    struct job *pjob = NULL;
    while (1)
    {
        pthread_mutex_lock(&(pool->mutex));
        pjob = pool->head;
        pool->queue_cur_num--;

        if(pool->queue_cur_num==0)
        {
            pool->head =pool->tail=NULL;
        }
        else
        {
            pool->head=pool->head->next;
        }

        pthread_mutex_unlock(&(pool->mutex));

        (*(pjob->func))(pjob->arg);
        free(pjob);
        pjob=NULL;
    }
}

但是我们在向任务队列添加任务前就已经创建好了线程池,也就是说线程会在任务添加进前已经运行。因此对于线程处理函数中pjob = pool->head;(第7行),如果开始时任务还没来得及添加进任务队列,会导致pool->head为NULL,从而导致 pool->head=pool->head->next; 以及 (*(pjob->func))(pjob->arg);发生段错误。

因此还需要进行判断任务队列是否为空,使用条件变量进行阻塞,等待任务添加进来

void *threadpool_function(void *arg)
{
    struct threadpool *pool = (struct threadpool *)arg;
    struct job *pjob = NULL;
    while (1)
    {
        pthread_mutex_lock(&(pool->mutex));
        //判断任务队列是否为空
        while(pool->queue_cur_num == 0)
        {
            pthread_cond_wait(&(pool->queue_not_empty),&(pool->mutex));
        }
        pjob = pool->head;
        pool->queue_cur_num--;

        if(pool->queue_cur_num==0)
        {
            pool->head =pool->tail=NULL;
        }
        else
        {
            pool->head=pool->head->next;
        }

        pthread_mutex_unlock(&(pool->mutex));

        (*(pjob->func))(pjob->arg);
        free(pjob);
        pjob=NULL;
    }
}

然后在添加任务队列函数中,如果任务队列为空就进行广播,解阻塞

    void threadpool_add_job(struct threadpool *pool, void *(*func)(void *arg), void *arg)
    {
    ...
    if (pool->head == NULL)
    {
        pool->head = pool->tail = pjob;
        pthread_cond_broadcast(&(pool->queue_not_empty));
    }
    ...
    }

在main函数中创建10个任务

int main(int argc, char const *argv[])
{
    struct threadpool *pool = threadpool_init(10, 100);

    threadpool_add_job(pool,work,"1");
    threadpool_add_job(pool,work,"2");
    threadpool_add_job(pool,work,"3");
    threadpool_add_job(pool,work,"4");
    threadpool_add_job(pool,work,"5");
    threadpool_add_job(pool,work,"6");
    threadpool_add_job(pool,work,"7");
    threadpool_add_job(pool,work,"8");
    threadpool_add_job(pool,work,"9");
    threadpool_add_job(pool,work,"10");

    while(1);
    return 0;
}

运行效果如下:

所有的任务都被执行了,执行顺序由线程调度器的时间片调度决定

对于任务队列已满的条件变量我们还需要在线程处理函数中判断进行解阻塞

void threadpool_add_job(struct threadpool *pool, void *(*func)(void *arg), void *arg)
{
... ...
//如果任务队列已满
while(pool->queue_cur_num == pool->queue_max_num)
{
    pthread_cond_wait(&(pool->queue_not_full),&(pool->mutex));
}
...
}

线程处理函数

void *threadpool_function(void *arg)
{
    struct threadpool *pool = (struct threadpool *)arg;
    struct job *pjob = NULL;
    while (1)
    {
        pthread_mutex_lock(&(pool->mutex));

        while(pool->queue_cur_num == 0)
        {
            pthread_cond_wait(&(pool->queue_not_empty),&(pool->mutex));
        }
        pjob = pool->head;
        pool->queue_cur_num--;
        //对任务队列满队条件变量解阻塞
        if(pool->queue_cur_num!=pool->queue_max_num)
        {
            pthread_cond_broadcast(&(pool->queue_not_full));
        }

        if(pool->queue_cur_num==0)
        {
            pool->head =pool->tail=NULL;
        }
        else
        {
            pool->head=pool->head->next;
        }

        pthread_mutex_unlock(&(pool->mutex));

        (*(pjob->func))(pjob->arg);
        free(pjob);
        pjob=NULL;
    }
}

至此线程池的全部实现已完成80%,我们还需要对线程池进行线程资源释放等操作,如果不销毁会导致僵尸线程。

线程池资源释放

我们一定要当任务队列为空的时候,才能对线程池进行销毁,因此我们在线程销毁函数里要使用任务为空的条件变量进行阻塞等待

void thread_destroy(struct threadpool *pool)
{
    pthread_mutex_lock(&(pool->mutex));
    while(pool->queue_cur_num !=0)
    {
        pthread_cond_wait(&(pool->queue_empty),&(pool->mutex));
    }
    pthread_mutex_unlock(&(pool->mutex));
... ...
}

在线程函数中,当判断任务数量为0时,进行信号量解阻塞

...
if(pool->queue_cur_num==0)
{
    pool->head =pool->tail=NULL;
    pthread_cond_broadcast(&(pool->queue_empty));
}
...

而当任务队列里任务为空时,所有线程都将阻塞:

因此我们在要销毁线程的时候,通知所有阻塞的线程继续执行(可省略),线程池资源回收函数如下(其中要注意使用pthread_cancel进行线程退出时线程需要有系统调用,如sleep):

void thread_destroy(struct threadpool *pool)
{
    pthread_mutex_lock(&(pool->mutex));
    while(pool->queue_cur_num !=0)
    {
        pthread_cond_wait(&(pool->queue_empty),&(pool->mutex));
    }
    pthread_mutex_unlock(&(pool->mutex));
    //通知所有阻塞的线程
    pthread_cond_broadcast(&(pool->queue_not_empty));
    pthread_cond_broadcast(&(pool->queue_not_full));//可不要

    for(int i=0;i<pool->thread_num;i++)
    {
        printf("thread exit!\n");
        pthread_cancel(pool->pthread_ids[i]);
        pthread_join(pool->pthread_ids[i],NULL);
    }

    pthread_mutex_destroy(&(pool->mutex));
    pthread_cond_destroy(&(pool->queue_empty));
    pthread_cond_destroy(&(pool->queue_not_empty));
    pthread_cond_destroy(&(pool->queue_not_full));

    free(pool->pthread_ids);

    //为了以防万一,任务队列不为空,要对所有任务进行销毁
    struct job *temp;
    while(pool->head!=NULL)
    {
        temp = pool->head;
        pool->head=temp->next;
        free(temp);
    }

    free(pool);
}

线程池完整程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
struct job
{
    void *(*func)(void *arg);
    void *arg;
    struct job *next;
};

struct threadpool
{
    int thread_num;         //已开启的线程数量
    pthread_t *pthread_ids; //保存线程池中线程的id

    struct job *head;  //任务队列的头
    struct job *tail;  //任务队列的尾
    int queue_max_num; //任务队列的最大数
    int queue_cur_num; //任务队列已有多少个任务

    pthread_mutex_t mutex;
    pthread_cond_t queue_empty;     //控制任务队列为空的条件
    pthread_cond_t queue_not_empty; //控制任务队列不为空的条件
    pthread_cond_t queue_not_full;  //控制任务队列不为满的条件
};

void *threadpool_function(void *arg)
{
    struct threadpool *pool = (struct threadpool *)arg;
    struct job *pjob = NULL;
    while (1)
    {
        pthread_mutex_lock(&(pool->mutex));

        while(pool->queue_cur_num == 0)
        {
            pthread_cond_wait(&(pool->queue_not_empty),&(pool->mutex));
        }
        pjob = pool->head;
        pool->queue_cur_num--;
        //对任务队列满队条件变量解阻塞
        if(pool->queue_cur_num!=pool->queue_max_num)
        {
            pthread_cond_broadcast(&(pool->queue_not_full));
        }

        
        if(pool->queue_cur_num==0)
        {
            pool->head =pool->tail=NULL;
            pthread_cond_broadcast(&(pool->queue_empty));
        }
        else
        {
            pool->head=pool->head->next;
        }

        pthread_mutex_unlock(&(pool->mutex));

        (*(pjob->func))(pjob->arg);
        free(pjob);
        pjob=NULL;
    }
}

struct threadpool *threadpool_init(int thread_num, int queue_max_num)
{
    struct threadpool *pool = (struct threadpool *)malloc(sizeof(struct threadpool));
    // malloc
    pool->queue_max_num = queue_max_num;
    pool->queue_cur_num = 0;
    pool->head = NULL;
    pool->tail = NULL;

    pthread_mutex_init(&(pool->mutex), NULL);
    pthread_cond_init(&(pool->queue_empty), NULL);
    pthread_cond_init(&(pool->queue_not_empty), NULL);
    pthread_cond_init(&(pool->queue_not_full), NULL);

    pool->thread_num = thread_num;
    pool->pthread_ids = malloc(sizeof(pthread_t) * thread_num);
    // malloc

    for (int i = 0; i < pool->thread_num; i++)
    {
        pthread_create(&(pool->pthread_ids[i]), NULL, threadpool_function, (void *)pool);
    }
    return pool;
}

void threadpool_add_job(struct threadpool *pool, void *(*func)(void *arg), void *arg)
{
    pthread_mutex_lock(&(pool->mutex));
    //如果任务队列已满
    while(pool->queue_cur_num == pool->queue_max_num)
    {
        pthread_cond_wait(&(pool->queue_not_full),&(pool->mutex));
    }
    struct job *pjob = (struct job *)malloc(sizeof(struct job));

    pjob->func = func;
    pjob->arg = arg;

    if (pool->head == NULL)
    {
        pool->head = pool->tail = pjob;
        pthread_cond_broadcast(&(pool->queue_not_empty));
    }
    else
    {
        pool->tail->next = pjob;
        pool->tail = pjob;
    }
    pool->queue_cur_num++;
    pthread_mutex_unlock(&(pool->mutex));
}

void thread_destroy(struct threadpool *pool)
{
    pthread_mutex_lock(&(pool->mutex));
    while(pool->queue_cur_num !=0)
    {
        pthread_cond_wait(&(pool->queue_empty),&(pool->mutex));
    }
    pthread_mutex_unlock(&(pool->mutex));
    //通知所有阻塞的线程
    pthread_cond_broadcast(&(pool->queue_not_empty));
    pthread_cond_broadcast(&(pool->queue_not_full));//可不要

    for(int i=0;i<pool->thread_num;i++)
    {
        printf("thread exit!\n");
        pthread_cancel(pool->pthread_ids[i]);
        pthread_join(pool->pthread_ids[i],NULL);
    }

    pthread_mutex_destroy(&(pool->mutex));
    pthread_cond_destroy(&(pool->queue_empty));
    pthread_cond_destroy(&(pool->queue_not_empty));
    pthread_cond_destroy(&(pool->queue_not_full));

    free(pool->pthread_ids);

    //为了以防万一,任务队列不为空,要对所有任务进行销毁
    struct job *temp;
    while(pool->head!=NULL)
    {
        temp = pool->head;
        pool->head=temp->next;
        free(temp);
    }

    free(pool);
}

void *work(void *arg)
{
    char *p = (char *)arg;
    printf("hello world! %s\n", p);
    printf("welcome to Nanjing! %s\n", p);
    sleep(1);
}

int main(int argc, char const *argv[])
{
    struct threadpool *pool = threadpool_init(10, 100);

    threadpool_add_job(pool,work,"1");
    threadpool_add_job(pool,work,"2");
    threadpool_add_job(pool,work,"3");
    threadpool_add_job(pool,work,"4");
    threadpool_add_job(pool,work,"5");
    threadpool_add_job(pool,work,"6");
    threadpool_add_job(pool,work,"7");
    threadpool_add_job(pool,work,"8");
    threadpool_add_job(pool,work,"9");
    threadpool_add_job(pool,work,"10");

    threadpool_add_job(pool,work,"11");
    threadpool_add_job(pool,work,"12");
    threadpool_add_job(pool,work,"13");
    threadpool_add_job(pool,work,"14");
    threadpool_add_job(pool,work,"15");
    threadpool_add_job(pool,work,"16");
    threadpool_add_job(pool,work,"17");
    threadpool_add_job(pool,work,"18");
    threadpool_add_job(pool,work,"19");
    threadpool_add_job(pool,work,"20");

    thread_destroy(pool);
    sleep(5);
    return 0;
}

线程池v1.0版本总结

我们现在实现的线程池,比如线程数量定义为10,如果任务数量很多的情况下就会导致线程池运行效率下降。而如果定义的线程数量很多,而任务数量很少,就会导致资源浪费。

概况来说就是以下两个问题:

  • 线程数量小,但任务多,导致效率下降
  • 线程数量多,但任务少,导致资源浪费

我们可以根据任务的多少来动态分配线程池中线程的个数:当任务队列里,任务很多,但线程数量很少的时候,我们就往线程池里增加线程;而当任务队列里,任务数量少,但线程数量多的情况,我们就关闭一些线程。

线程池的v1.0版本到此全部介绍结束,后面就来实现可伸缩性线程池。

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

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

相关文章

mysql-sql性能分析工具

一、sql执行频率 MySQL 客户端连接成功后&#xff0c;通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次&#xff1a; -- session 是查看当前会话 ; -- global 是查询全…

uni-app 打包生成签名Sha1

Android平台打包发布apk应用&#xff0c;需要使用数字证书&#xff08;.keystore文件&#xff09;进行签名&#xff0c;用于表明开发者身份。 可以使用JRE环境中的keytool命令生成。以下是windows平台生成证书的方法&#xff1a; 安装JRE环境&#xff08;推荐使用JRE8环境&am…

树结构使用实例---实现数组和树结构的转换

文章目录 一、为什么要用树结构&#xff1f;二、使用步骤 1.引入相关json2.树结构的转换总结 一、为什么要用树结构&#xff1f; 本文将讲述一个实例&#xff0c;构造一棵树来实现数组和tree的转换&#xff0c;这在前端树结构中是经常遇到的 后端返回树结构方便管理&#xff…

OpenCL矢量加法例子

1.向量加内核函数 __kernel void vector_add(__global float* A, __global float* B, __global float* C) {int id get_global_id(0);C[id] A[id] B[id]; }2.向量加主程序 #include<stdio.h> #include<stdlib.h> #ifdef _APPLE_ #include<OpenCL/cl.h> …

通过DBeaver 给Postgre SQL表 设置主键自增

1.创建表 CREATE TABLE public.company ( id int4 NOT NULL , name text NOT NULL, age int4 NOT NULL, address bpchar(50) NULL, salary float4 NULL, join_date date NULL, CONSTRAINT company_pkey PRIMARY KEY (id) ); 2.插入数据&#xff08;不传入id&#xff…

探索高效的HTTP异步接口测试方法:从轮询等待到自动化方案

本文将深入探讨HTTP异步接口测试的多个方面&#xff0c;包括轮询等待、性能测试以及自动化方案。通过详细的解释和实际案例&#xff0c;帮助您了解如何有效地测试异步接口&#xff0c;确保系统的稳定性和性能。 在现代软件开发中&#xff0c;HTTP异步接口扮演着至关重要的角色&…

电子电路学习笔记之SA1117BH-1.2TR——LDO低压差线性稳压器

关于LDO调节器&#xff08;Low Dropout Regulator&#xff09;是一种电压稳压器件&#xff0c;常用于电子设备中&#xff0c;用于将高电压转换为稳定的低电压。它能够在输入电压和输出电压之间产生较小的差异电压&#xff0c;因此被称为"低压差稳压器"。 LDO调节器通…

PHP自己的框架自定义错误器set_error_handler和register_shutdown_function(完善篇五)

1、PHP自己的框架实现错误显示和记录日志 2、运行时错误set_error_handler和致命错误register_shutdown_function KJ.php public static function run(){//定义常量self::_set_const();//创建模块目录self::_mk_module();//加载文件self::_import_file();self::_set_system();s…

安卓机显示屏的硬件结构

显示屏的硬件结构 显示屏的硬件结构主要由背光源、液晶面板和驱动电路构成。可以将液晶面板看成一个三明治的结构&#xff0c;即在两片偏振方向互相垂直的偏光片系统中夹着一层液晶层。自然光源通过起偏器&#xff08;偏光片之一&#xff09;后&#xff0c;变成了垂直方向的偏…

短视频矩阵seo关键词霸屏开发源码逻辑

目录 ​编辑 一、短视频矩阵系统seo是如何从开发上实现的 二、短视频SEO矩阵系统源码开发&#xff0c;需要遵循一下步骤&#xff1a; 三、矩阵开发源码&#xff0c;功能开发&#xff0c;代码展示如下&#xff1a; 一、短视频矩阵系统seo是如何从开发上实现的 短视频矩阵系…

陕西广电 HG6341C FiberHome烽火 光猫获取超级密码 改桥接模式 提升网速

光猫默认的路由模式实测在100M宽带下只能跑到60M左右&#xff0c;只有改成桥接模式才能跑满&#xff0c;不损失性能。但是改桥接需要给运营商打电话&#xff0c;有的时候不想麻烦他们&#xff0c;这时获取超级密码进行更改就是一个不错的选择了 分析 之前写了一篇HGU B2 光猫的…

微服务流程引擎:简单又灵活,实现流程全生命周期管理!

伴随着日益激烈的市场竞争&#xff0c;传统的办公操作已经无法满足发展需要了。如果采用微服务流程引擎加油助力&#xff0c;就可以帮助企业更好地管理数据资源&#xff0c;高效做好各种表单制作&#xff0c;实现高效率办公。流辰信息以市场为导向&#xff0c;用心钻研低代码技…

二、5.单线程与多线程调度

任务轮转工作由任务调度器来完成的&#xff0c;任务调度器就是操作系统中用于把任务轮流调度上处理器运行的一个软件模块&#xff0c;它是操作系统的一部分。调度器在内核中维护一个任务表&#xff08;也称进程表、线程表或调度表&#xff09;&#xff0c;然后按照一定的算法&a…

2022年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;和数 给定一个正整数序列&#xff0c;判断其中有多少个数&#xff0c;等于数列中其他两个数的和。 比如&#xff0c;对于数列1 2 3 4, 这个问题的答案就是2, 因为3 2 1, 4 1 3。 时间限制&#xff1a;10000 内存限制&#xff1a;65536 输入 共两行&#x…

Lucky player —— Java 项目(Spring Boot)

一、项目介绍 项目名称&#xff1a;lucky player 项目的主要功能&#xff1a;本系统主要功能为构建了一个用户分享音乐的平台&#xff0c;普通用户不进行登录即可收听其他用户已经发布的专辑中的音乐。 作为博主则可以在该平台上传音频&#xff0c;以及在线音频录制上传。音频上…

Kafka 学习笔记

&#x1f600;&#x1f600;&#x1f600;创作不易&#xff0c;各位看官点赞收藏. 文章目录 Kafka 学习笔记1、消息队列 MQ2、Kafka 下载安装2.1、Zookeeper 方式启动2.2、KRaft 协议启动2.3、Kafka 集群搭建 3、Kafka 之生产者3.1、Java 生产者 API3.2、Kafka 生产者生产分区3…

8月17日上课内容 第三章 LVS+Keepalived群集

本章结构 Keepalived概述 keepalived 概述 1.服务功能 故障自动切换 健康检查 节点服务器高可用 HA keepalived工作原理 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题 在一个LVS服务集群中通常有主服务器 (MAST…

【AWS】安装配置适用于 Eclipse 的 AWS 工具包

目录 0.环境 1.步骤 1&#xff09;安装Eclipse 2&#xff09;安装AWS工具包 ① 在这个路径下点开安装软件的界面 ② 点击【Add】打开添加窗口 ③ 输入aws的工具包地址 ④ 勾选需要的工具&#xff0c;点击【Next】 ⑤ 将要安装的工具&#xff0c;点击【Next】 ⑥ 选择接受…

华为云CodeArts Snap 智能编程助手PyCharm实验手册. 插件安装与使用指南

作为一款自主创新的AI代码辅助编程工具&#xff0c;华为云智能编程助手CodeArts Snap目标打造现代化开发新范式。通过将自然语言转化为规范可阅读、无开源漏洞的安全编程语言&#xff0c;提升开发者编程效率&#xff0c;助力企业快速响应市场需求。华为云CodeArts Snap现进入邀…

【算法C++实现】5、二叉树

二叉树节点结构体 class Node { public:int val;Node* left;Node* right;Node(int a) : val(a), left(nullptr), right(nullptr) {}Node(int a, Node* l, Node* r): val(a), left(l), right(r) {}}1、递归遍历 递归遍历二叉树&#xff0c;每个节点的遍历顺序叫递归序&#xf…