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

目录

线程池的实现过程

线程池的创建

添加任务队列

线程进行处理任务

线程池资源释放

线程池完整程序

线程池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; //保存线程池中线程的idstruct 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));//mallocint 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));//mallocpool->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);//mallocfor(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; //保存线程池中线程的idstruct 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));// mallocpool->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);// mallocfor (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.mzph.cn/news/47133.shtml

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

相关文章

Android笔记:在原生App中嵌入Flutter

首先有一个可以运行的原生项目 第一步&#xff1a;新建Flutter module Terminal进入到项目根目录&#xff0c;执行flutter create -t module ‘module名字’例如&#xff1a;flutter create -t module flutter-native 执行完毕&#xff0c;就会发现项目目录下生成了一个modu…

Android Drawable转BitmapDrawable再提取Bitmap,Kotlin

Android Drawable转BitmapDrawable再提取Bitmap&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"…

MySQL——基础——内连接

一、内连接查询语法 隐式内连接 SELECT 字段列表 FROM 表1&#xff0c;表2 WHERE 条件...; 显示内连接 SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 连接条件...; 内连接查询的是两张表交集的部分 二、内连接演示 1.查询每一个员工的姓名&#xff0c;及关联的部门的名称…

Linux学习之ftp安装、vsftpd安装和使用

ftp需要两个端口&#xff1a; 数据端口 命令端口 ftp有两种模式&#xff1a; 被动模式&#xff1a;建立命令连接之后&#xff0c;服务器等待客户端发起请求。 主动模式&#xff1a;建立命令连接之后&#xff0c;服务器主动向客户端发起数据连接&#xff0c;因为客户端可能有防火…

12312321312

目录 层次分析法(AHP) 基本步骤 建立层次模型 构造判断矩阵 一致性检验 求得权重 填表得结果 一点补充 详细做法补充 特征向量含义思考 一些问题 优劣解距离法(TOPSIS) 基本思想 模型步骤 数据处理 指标正向化 标准化处理 计算得分 *结果处理 熵权法 模型思…

Flask入门一 ——虚拟环境及Flask安装

Flask入门一 ——虚拟环境及Flask安装 在大多数标准中&#xff0c;Flask都算是小型框架&#xff0c;小到可以称为“微框架”&#xff0c;但是并不意味着他比其他框架功能少。Flask自开发伊始就被设计为可扩展的框架。Flask具有一个包含基本服务的强健核心&#xff0c;其他功能…

【Django】Task4 序列化及其高级使用、ModelViewSet

【Django】Task4 序列化及其高级使用、ModelViewSet Task4主要了解序列化及掌握其高级使用&#xff0c;了解ModelViewSet的作用&#xff0c;ModelViewSet 是 Django REST framework&#xff08;DRF&#xff09;中的一个视图集类&#xff0c;用于快速创建处理模型数据的 API 视…

数据仓库一分钟

数据分层 一、数据运营层&#xff1a;ODS&#xff08;Operational Data Store&#xff09; “面向主题的”数据运营层&#xff0c;也叫ODS层&#xff0c;是最接近数据源中数据的一层&#xff0c;数据源中的数据&#xff0c;经过抽取、洗净、传输&#xff0c;也就说传说中的 ETL…

电信不提供公网IP怎么解决?快解析内网穿透解决方案

由于现在电信运营商的政策调整&#xff0c;加上受到网络服务架构的影响&#xff0c;一些用户在使用宽带连接时&#xff0c;往往会遇到电信不提供公网IP的情况。这种情况下&#xff0c;我们可能会受到一些限制&#xff0c;特别是对于需要对外提供服务或进行远程访问的场景而言&a…

使用ChatGPT-4优化编程效率:高效查询代码示例和解决方案

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Linux下的thundersvm的安装经验

之前写过一篇文章是关于windows下安装thundersvm的。当初在linux下安装thundersvm很容易&#xff0c;因为我的CUDA正好是9.0版本。所以一句命令 pip install thundersvm 即可安装。 但如果CUDA版本不是9.0&#xff0c;安装就比较麻烦。本文记录的是一种可行的方法&#xff0c;但…

LVS+Keepalived 实验

Keepalived 是什么 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可以解决静态路由出现的单点故障问题的一款检查工具 在一个LVS服务集群中通常有主服务器&#xff08;MASTER&#xff09;和备份服务器&#xff08;BACKUP&#xff09;两种角色的服务器…

Git标签

Git 中的标签&#xff0c;指的是某个分支某个特定时间点的状态(静态)。通过标签&#xff0c;可以很方便的切换到标记时的状态。 比较有代表性的是人们会使用这个功能来标记发布结点 (v1.0、v1.2等)。 下面是myatis-plus的标签: 1 标签相关命令 命令作用git tag查看标签&…

python中(限小白,大佬勿入)python开发中的trick:常量

开场白 我是小白&#xff0c;今天被前端骂了一顿&#xff1a;我们交接不是说好了就给你四个变量&#xff1a;A&#xff0c;B。C。D。你这命名的这么具体&#xff0c;我这边给你传值不是很方便啊&#xff08;因为不同模块有复用的图片路径&#xff09;&#xff0c;我说“那我改&…

自动化测试工具Selenium的语法续.

OK&#xff0c;那么上篇博客我们介绍了如何搭建基于Javaselenium的环境&#xff0c;并且使用selenium的一些语法给大家演示了如何进行自动化测试的案例&#xff0c;那么本篇博客我们来继续学习selenium的一些其他的比较重要的语法&#xff0c;感谢关注&#xff0c;期待三连~ 目…

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

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

CSS和AJAX阶段学习记录

1、AJAX的工作原理&#xff1a; 如图所示&#xff0c;工作原理可以分为以下几步&#xff1a; 网页中发生一个事件&#xff08;页面加载、按钮点击&#xff09; 由 JavaScript 创建 XMLHttpRequest 对象 XMLHttpRequest 对象向 web 服务器发送请求 服务器处理该请求 服务器将响应…

音视频技术开发周刊 | 307

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 “视象新生”火山引擎视频云&AIGC技术大会邀你踏入新“视界” 8月22日&#xff0c;火山引擎视频云&AIGC技术大会即将开启&#xff01;本次大会以“视象新生”为主…

第 359 场 LeetCode 周赛题解

A 判别首字母缩略词 签到题… class Solution { public:bool isAcronym(vector<string> &words, string s) {string pf;for (auto &s: words)pf.push_back(s[0]);return pf s;} };B k-avoiding 数组的最小总和 贪心&#xff1a;从 1 1 1开始升序枚举&#xff0c…

第七次作业 运维高级 docker容器进级版

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 (1)拉取相应镜像 docker pull mysq:5.6 docker pull owncloud:latest(2)运行mysql&#xff1a;5.6容器 docker run --name mysql -e MYSQL_ROOT_PASSWORD12345 -d mysql:5.6(3)运行owncloud容器 docker run…