Linux系统编程系列之线程池

news2024/10/6 0:30:12

 Linux系统编程系列(16篇管饱,吃货都投降了!)

        1、Linux系统编程系列之进程基础

        2、Linux系统编程系列之进程间通信(IPC)-信号

        3、Linux系统编程系列之进程间通信(IPC)-管道

        4、Linux系统编程系列之进程间通信-IPC对象

        5、Linux系统编程系列之进程间通信-消息队列

        6、Linux系统编程系列之进程间通信-共享内存

        7、Linux系统编程系列之进程间通信-信号量组

        8、Linux系统编程系列之守护进程

        9、Linux系统编程系列之线程

        10、Linux系统编程系列之线程属性 

        11、Linux系统编程系列之互斥锁和读写锁

        12、Linux系统编程系列之线程的信号处理

        13、Linux系统编程系列之POSIX信号量

        14、Linux系统编程系列之条件变量

        15、Linux系统编程系列之死锁

        16、 Linux系统编程系列之线程池

一、什么是线程池

        线程池就是将许多线程,放置在一个池子中(实际就是一个结构体)只要有任务,就将任务投入池中,这些线程们通过某些机制,及时处理这些任务,一旦处理完后又重新回到池子中重新接收新任务。为了便于管理,线程池还应当提供诸如初始化线程池,增删线程数量,检测未完成任务的数据,检测正在执行任务的线程的数量、销毁线程池等等基本操作。

二、特性

        1、更好的满足并发性需求

        2、更加节省系统资源,降低负载

        3、避免每次需要执行任务时都创建新的线程

        4、可以限制并发线程数量,帮助控制应用程序的性能和稳定性

        核心思想:通过精巧的设计使得池子中的线程数量可以动态地发生变化,让线程既可以应对并发性需求,又不会浪费系统资源

三、使用场景

        用来应对某种场景,要在程序中创建大量线程,并且这些线程的数量和生命周期均不确定,可能方生方死,也可能常驻内存,请看下面举例。

        1、处理大量密集型任务

        线程池可以处理多个任务,从而减少了创建和销毁线程的开销,提高了系统性能

        2、服务端应用程序

        线程池可以帮助服务端应用程序同时处理多个客户端请求

        3、I/O 密集型应用程序

        线程池可以处理 I/O 操作,允许系统使用空闲时间进行其他任务处理

        4、Web 应用程序

        线程池可以处理来自 Web 客户端的请求,从而提高了 Web 应用程序的性能

        总之,任何需要处理大量任务或需要同时处理多个请求的应用程序都可以使用线程池来提高性能和效率。

四、设计思想

        设计某个东西一定要有个目标,这里是要求设计一个线程池,首先要明白线程池的作用,然后根据其作用来展开设计。线程池是通过让线程的数量进行动态的变化来及时处理接受的任务,所以核心就是线程和任务。可以将问题转换为如何组织线程和组织任务?借鉴别人的一幅图

         1、如何组织线程

                从上图可以看出,线程被创建出来之后,都处于睡眠态,它们实际上是进入了条件变量的等待队列中,而任务都被放入一个链表,被互斥锁保护起来

                线程的生命周期如下:(这里省略程序执行的流程图)

                (1)、线程被创建

                (2)、预防死锁,准备好退出处理函数,防止在持有一把锁的状态中死去

                (3)、尝试持有互斥锁(等待任务)

                (4)、判断是否有任务,若无则进入条件变量等待队列睡眠,若有则进入第(5)步

                (5)、从任务链表中取得一个任务

                (6)、释放互斥锁

                (7)、弹出退出处理函数,避免占用内存

                (8)、执行任务

                (9)、执行任务完成后重新回到第(2)步

                线程的生命周期其实就是线程要做的事情,把上面的第(2)步到第(8)步写成一个线程的例程函数。

         2、如何组织任务

                所谓的任务就是一个函数,回想一下创建一条线程时,是不是要指定一个线程例程函数。这里的线程池做法是将函数(准确的说是函数指针)及其参数存入一个任务节点,并将节点链接成一个链表。所以现在的问题变成如何设计一个任务链表?

                一个链表的操作有:创建节点,增加节点,删除节点,查询节点这几步。由于这里是任务节点,就不需要查询了,当然也可以查询。所以现在的问题转换为任务节点如何设计?

                任务节点的数据域主要有函数指针和函数参数,指针域就是一个简单任务结点指针,也可以是双向循环链表。

                设计分析到这里,基本上有了框架了,前面买了这么多关子,下面就来个重头戏。其实前面主要是想描述一种设计思想,遇到新的东西如何设计它?首先要明白设计的目的和作用,然后找出核心点,最后就是把核心点进行不断的分析推理,一步步转换为最基本的问题。学过项目管理的同学应该听过在范围管理里有个叫工作分解结构的东西,这里跟那个差不多。

五、案例

        实现一个线程池,并完成线程池的基本操作演示

        thread_pool.h(线程池头文件)

#ifndef _THREAD_POOL_H
#define _THREAD_POOL_H

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>

#define MAX_WAITING_TASKS	1000    // 最大的等待任务数量
#define MAX_ACTIVE_THREADS	20      // 最多的活跃线程数量

// 任务节点
typedef struct task 
{
    void *(*do_task) (void *arg);   // 函数指针
    void *arg;      // 函数参数
    struct task *next;  // 指向下一个任务节点
}task;


// 线程池的管理节点
typedef struct thread_pool
{
    pthread_mutex_t task_list_lock;   // 任务列表互斥锁,保证同一个时间只有一条线程进行访问
    pthread_cond_t  task_list_cond;   // 任务列表条件变量,保证没有任务时,线程进入睡眠

    task *task_list;    // 任务列表

    pthread_t *tids;     // 存放线程号的数组

    int shutdown;  // 线程池销毁的开关

    unsigned int max_waiting_tasks;  // 最大等待的任务数量
	unsigned int waiting_tasks;      // 正在等待被处理的任务数量
	unsigned int active_threads;     // 正在活跃的线程数

}thread_pool;

// 初始化线程池
int init_pool(thread_pool *pool, unsigned int threads_number);

// 往线程池中添加任务
int add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg);

// 往线程池中添加线程
int add_thread(thread_pool *pool, unsigned int additional_threads);

// 从线程池中删除线程
int remove_thread(thread_pool *pool, unsigned int removing_threads);

// 查询线程池中线程数量
int total_thread(thread_pool *pool);

// 销毁线程池
int destroy_pool(thread_pool *pool);

// 线程的例程函数
void *routine(void *arg);

#endif // _THREAD_POOL_H

        thread_pool.c(线程池实现文件) 

#include "thread_pool.h"

// 初始化线程池
int init_pool(thread_pool *pool, unsigned int threads_number)
{
    if(threads_number < 1)
    {
        printf("warning: threads_number must bigger than 0\n");
        return -1;
    }
    if(threads_number > MAX_ACTIVE_THREADS)
    {
        printf("warning: threads number is bigger than MAX_ACTIVE_THREADS(%u)\n", MAX_ACTIVE_THREADS);
        return -1;
    }

    pthread_mutex_init(&pool->task_list_lock, NULL); // 初始化互斥锁
    pthread_cond_init(&pool->task_list_cond, NULL);  // 初始化条件变量

    pool->task_list = calloc(1, sizeof(task));  // 申请一个任务头节点
    if(!pool->task_list)
    {
        printf("error: task_list calloc fail\n");
        return -1;
    }
    pool->task_list->next = NULL;   // 让任务头节点指向NULL

    pool->tids = calloc(MAX_ACTIVE_THREADS, sizeof(pthread_t)); // 申请堆数组用来存放线程号
    if(!pool->tids)
    {
        printf("error: tids calloc fail\n");
        return -1;
    }

    pool->shutdown = 0; // 关闭线程池销毁开发

    pool->waiting_tasks = 0;    // 当前等待任务为0
    pool->max_waiting_tasks = MAX_WAITING_TASKS;    // 初始化最大等待任务数量
    pool->active_threads = threads_number;  // 初始化活跃的线程数
   
    // 根据活跃的线程数,创建对应数量的线程
    for(int i = 0; i < pool->active_threads; i++)
    {
        // 需要判断线程是否创建失败
        // 线程号用线程号数组,线程属性设置为默认的,例程函数是routine,函数参数是线程池
        errno = pthread_create(&pool->tids[i], NULL, routine,(void*)pool);
        if(errno != 0)
        {
            perror("error: pthread_create fail");
            return -1;
        }
    }
    
    return 1;
}

// 往线程池中添加任务
int add_task(thread_pool *pool, void *(*do_task)(void *arg), void *arg)
{
    if(!pool)
    {
        printf("warning: thread_pool is null\n");
        return -1;
    }

    // 如果当前等待执行的任务数超过最大等待的任务数量,则退出
    if(pool->waiting_tasks >= pool->max_waiting_tasks)
    {
        printf("warning: task_list is full, too many list\n");
        return -1;
    }

    // 尝试给新的任务节点申请空间
    task *new_task = (task*)malloc(sizeof(task));
    if(!new_task)
    {
        printf("error: task malloc fail\n");
        return -1;
    }

    // 初始化任务节点
    new_task->do_task = do_task;
    new_task->arg = arg;
    new_task->next = NULL;

    // 把任务节点添加到任务列表最后面
    // 1、先找到任务列表末尾
    task *tmp = NULL;
    for(tmp = pool->task_list; tmp->next; tmp = tmp->next);

    // 2、上锁
    pthread_mutex_lock(&pool->task_list_lock);
    
    // 3、添加新任务到任务列表末尾,同时当前等待任务数+1
    tmp->next = new_task;
    pool->waiting_tasks++;

    // 4、解锁
    pthread_mutex_unlock(&pool->task_list_lock);

    // 唤醒一个正在条件变量中睡眠等待的线程,取执行任务
    pthread_cond_signal(&pool->task_list_cond);

    return 1;
}

// 往线程池中添加线程,返回实际添加的线程数量
int add_thread(thread_pool *pool, unsigned int additional_threads)
{
    if(!pool)
    {
        printf("warning: thread_pool is null\n");
        return -1;
    }

    if(additional_threads == 0)
    {
        return 0;
    }

    // 期望活跃的线程数 = 目前活跃的线程数 + 期望添加的线程数
    unsigned int total_threads = pool->active_threads + additional_threads;

    // 如果超过最大的活跃线程数就打印警告
    if(total_threads > MAX_ACTIVE_THREADS)
    {
        printf("warning: add too many threads\n");
    }

    int actual_add_threads = 0;
    for(int i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++)
    {
        // 需要判断线程是否创建失败
        // 线程号用线程号数组,线程属性设置为默认的,例程函数是routine,函数参数是线程池
        errno = pthread_create(&pool->tids[i], NULL, routine,(void*)pool);
        if(errno != 0)
        {
            perror("error: pthread_create fail");
            
            // 如果一个都没有创建成功,就直接返回
            if(actual_add_threads == 0)
            {
                return -1;
            }
            // 如果成功创建了多个线程,但是本次创建失败,就跳出循环
            break;
        }
        else
        {
            actual_add_threads++;   // 线程创建成功就+1
        }
    }

    // 更新线程池中活跃的线程数
    pool->active_threads += actual_add_threads;

    return actual_add_threads;  // 返回实际添加的线程
}

// 从线程池中删除线程,返回实际删除线程的数量
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
    if(!pool)
    {
        printf("warning: thread_pool is null\n");
        return -1;
    }

    if(removing_threads == 0)
    {
        return pool->active_threads;
    }

    // 如果要删除的线程数大于线程池中活跃的线程数,则打印警告
    if(removing_threads >= pool->active_threads)
    {
        printf("warning: remove too many threads\n");
    }

    // 剩余数量 = 活跃数量 - 删除的目标数  
	int remaining_threads = pool->active_threads - removing_threads;

	//  目的是为了让线程池中最少保留有一个线程可以用于执行任务
	remaining_threads = remaining_threads > 0 ? remaining_threads : 1;

    int actual_remove_threads = 0;;
    // 循环取消线程直到等于期望线程数
    for(int i = pool->active_threads-1; i > remaining_threads-1; i--)
    {
        errno = pthread_cancel(pool->tids[i]);
        if(errno != 0)
        {
            printf("[%ld] cancel error: %s\n", pool->tids[i], strerror(errno));
            break;
        }
        else
        {
            actual_remove_threads++;
        }
    }

    // 更新线程池中活跃的线程数量
    pool->active_threads -= actual_remove_threads;

    return actual_remove_threads;   // 返回实际删除的线程
}

// 查询线程池中线程数量
int total_thread(thread_pool *pool)
{
    return pool->active_threads;
}

// 销毁线程池
int destroy_pool(thread_pool *pool)
{
    if(!pool)
    {
        printf("warning: thread_pool is null\n");
        return -1;
    }

    pool->shutdown = 1; // 启动线程池销毁开关

    pthread_cond_broadcast(&pool->task_list_cond);    // 唤醒所有在线程池中的线程

    // 循环等待所有线程退出
    for(int i = 0; i < pool->active_threads; i++)
    {
        errno = pthread_join(pool->tids[i], NULL);
        if(errno != 0)
        {
            printf("join tids[%d] error: %s\n", i, strerror(errno));
        }
        else
        {
            printf("[%ld] is joined\n", pool->tids[i]);
        }
    }

    // 头删法,从头一个个的删除任务节点
    for(task *p = pool->task_list->next; p; p = pool->task_list)
    {
        pool->task_list->next = p->next;    // 修改首元节点
        free(p);
    }

    free(pool->task_list);
    free(pool->tids);
    free(pool);

    return 1;
}

// 线程的取消函数
void pthread_cancel_handler(void *arg)
{
    printf("[%ld] is cancel\n", pthread_self());

    pthread_mutex_unlock((pthread_mutex_t*)arg);    // 解锁
}

// 线程的例程函数
void *routine(void *arg)
{
    task *task_p;   // 任务指针,用来执行将要执行的任务

    thread_pool *pool = (thread_pool*)arg;

    while(1)
    {
        pthread_cleanup_push(pthread_cancel_handler, (void*)&pool->task_list_lock);

        // 尝试持有互斥锁
        pthread_mutex_lock(&pool->task_list_lock);

        // 判断是否有任务,没有则进入睡眠
        // 1、没有任务,线程池销毁开关断开,则进入条件变量等待队列
        while(!pool->waiting_tasks && !pool->shutdown)
        {
            // 当条件不满足时,会先自动解锁pool->lock,然后等待到条件满足后,会自动上锁pool->lock
            pthread_cond_wait(&pool->task_list_cond, &pool->task_list_lock);
        }

        // 2、没有任务,线程池销毁开发闭合,则先解锁,然后退出
        if(!pool->waiting_tasks && pool->shutdown)
        {
            pthread_mutex_unlock(&pool->task_list_lock);  // 需要解锁
            pthread_exit(NULL);
        }

        // 3、有任务,线程池销毁开关断开,则取一个任务
        task_p = pool->task_list->next;
        pool->task_list->next = task_p->next; // 弹出第一任务节点 
        pool->waiting_tasks--;  // 当前等待的任务数量-1,

        pthread_mutex_unlock(&pool->task_list_lock);  // 解锁

        // 弹出压入的线程取消函数,运行到这里不执行,但是当线程在前面被意外取消或中断会执行
        pthread_cleanup_pop(0); 

        // 为了防止死锁,执行任务期间不接受线程取消请求
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

        (task_p->do_task)(task_p->arg); // 通过函数指针的方式执行任务

        // 执行完任务后,接受线程取消请求
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

        free(task_p);   // 删除任务节点
    }

    pthread_exit(NULL);
}

        main.c(线程池操作演示文件) 

// 线程池的测试案例

#include <stdio.h>
#include <unistd.h>
#include "thread_pool.h"

void *mytask(void *arg)
{	
    int n = rand()%10;

	printf("[%ld][%s] ==> job will be done in %d sec...\n", pthread_self(), __FUNCTION__, n);
    
	sleep(n);

	printf("[%ld][%s] ==> job done!\n", pthread_self(), __FUNCTION__);
}

void *func(void *arg )
{
	printf("this is a test by [%s]\n", (char*)arg);
	sleep(1);
	printf("test finish...\n");
}

void *count_time(void *arg)
{
	int i = 0;
	while(1)
	{
		sleep(1);
		printf("sec: %d\n", ++i);
	}
}

int main(void)
{
	pthread_t a;
	pthread_create(&a, NULL, count_time, NULL);

	// 1、初始化线程池
	thread_pool *pool = malloc(sizeof(thread_pool));
	init_pool(pool, 2);

	// 2、投放任务
	printf("throwing 3 tasks...\n");
	add_task(pool, mytask, NULL);
	add_task(pool, mytask, NULL);
	add_task(pool, mytask, NULL);

	// 3、查看当前线程池中的线程数量
	printf("current thread number: %d\n", total_thread(pool));
	sleep(9);

	// 4、再次投放任务
	printf("throwing another 2 tasks...\n");
	add_task(pool, mytask, NULL);
	add_task(pool, mytask, NULL);
	add_task(pool, func, (void *)"Great Macro");

	// 5、添加2条线程
    printf("try add 2 threads, actual add %d\n", add_thread(pool, 2));
    printf("current thread number: %d\n", total_thread(pool));
	
	sleep(5);

	// 6、删除3条线程
	printf("try remove 3 threads, actual remove: %d\n", remove_thread(pool, 3));
    printf("current thread number: %d\n", total_thread(pool));

    sleep(5);
	// 7、 销毁线程池
    printf("destroy thread pool\n");
	destroy_pool(pool);
	return 0;
}

        注:编译时,把线程池文件和测试文件放在同一个工作目录下。

        实际使用时,只需要把thread_pool.h和thread_pool.c拷贝到自己的工程目录下,然后根据规则操作线程池。

六、总结

        线程池是许多线程的集合,它不是线程组。线程池适用于任何需要处理大量任务或需要同时处理多个请求的应用程序的场景,可以提供性能和效率。

        至此,Linux系统编程系列,16篇完结撒花,历时5天,这年中秋国庆没有假放!!!

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

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

相关文章

Linux文件查找、别名、用户组

1、文件查看: 查看/etc/passwd文件的第5行 2、文件查找 (1)在当前目录及子目录中&#xff0c;查找大写字母开头的txt文件 (2)在/etc及其子目录中&#xff0c;查找以数字开头的文件 (3)在$HOME目录及其子目录中&#xff0c;查找所有文本文件 (4)忽略文件名大小写查找a.txt 3、查…

博途1200/1500 ALT指令

SMART PLC的ALT指令实现代码,请查看下面文章博客 SMART PLC如何构造ALT指令_smart200类似alt指令-CSDN博客单按钮启停这些老生常谈的问题,很多人感兴趣。这篇博文讨论下不同的实现方法,希望对大家有所帮助。指令虽然简单,但是在编程的时候合理使用对我们高效率编程帮助还是…

来聊一聊独热码检测

国庆假期不小心扭伤了脚踝&#xff0c;在家没事看到一篇文章挺有意思&#xff0c;于是写出来分享给大家。 这是一道数字电路面试题&#xff0c;也是很多面试官很喜欢考察面试者的一道题目&#xff0c;题干很简单&#xff1a;给定一个4bit的信号A&#xff0c;设计逻辑来判断A是…

博客之站项目测试报告

项目背景项目功能测试计划Bug总结升级自动化测试正常登录流程 项目背景 1&#xff1a;博客之站系统是采用前后端分离的方式来实现&#xff1b;使用MySQL、Redis数据库储存相关数据&#xff1b;同时部署到云服务器上。 2&#xff1a;包含注册页、登录页、博客列表页、个人列表页…

计算机考研 | 2020年 | 计算机组成原理真题

文章目录 【计算机组成原理2020年真题43题-13分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2020年真题44题-10分】【第一步&#xff1a;信息提取】【第二步&#xff1a;具体解答】 【计算机组成原理2020年真题43题-13分】 【第一步&…

vertx的学习总结6之动态代理类和测试

Beyond the event bus 一、章节覆盖&#xff1a; 如何在事件总线之上公开服务 verticles和事件总线服务的异步测试 动态代理&#xff1a; MyService 接口 package porxy.test;import io.vertx.codegen.annotations.ProxyGen;ProxyGen public interface MyService {void he…

基于ssm的电商管理平台/基于javaweb的网上购物系统/电子商城网站

摘 要 本文论述了电商管理平台的设计和实现&#xff0c;该网站从实际运用的角度出发&#xff0c;运用了计算机网站设计、数据库等相关知识&#xff0c;基于SSM框架、JSP技术和Mysql数据库设计来实现的&#xff0c;网站主要包括用户注册、用户登录、查看商品信息、系统公告等…

优化方法的应用(optimtool.example)

import optimtool as oo from optimtool.base import np, sp, pltpip install optimtool>2.4.2优化方法的应用&#xff08;optimtool.example&#xff09; import optimtool.example as oeLasso问题&#xff08;Lasso&#xff09; oe.Lasso.[函数名]([矩阵A], [矩阵b], [因…

Linux删除空目录/非空目录和文件

一、删除目录 删除名为mydir的空目录: rmdir mydir 删除名为mydir的非空目录(非空目录是指该目录包含了其他文件或子目录&#xff0c;而不是空的或没有任何内容的目录) rm -r mydir 删除mydir1下的空目录mydir2 rmdir mydir1/mydir2 删除当前目录下所有以dir一个数字结尾的目录…

打开MySQL数据库

在命令行里输入mysql --version就可以查看&#xff1a; mysql -uroot -p之前设置的密码&#xff08;不用输入&#xff09;就可登录成功&#xff1a;

什么是邮件签名证书?

邮件签名证书是一种数字证书&#xff0c;用于保护电子邮件的安全性和可信度。邮件签名证书可以确保电子邮件本身在发送过程中不会被篡改或伪造&#xff0c;同时也可以验证发件人的身份。 邮件签名证书的工作原理是使用S/MIME协议。S/MIME协议是一种用于加密和签名电子邮件的标准…

tio-websocket-spring-boot-starter的最简单实例,看完你一定有所收获

前言 我最近一个月一直在寻找能够快速开发实时通讯的简单好用的模块,所以我就去寻找了一下相关的内容.在此之前我使用的是Spring原生的webSocket,她有个弊端就是设置组不容易设置,而且配置上也稍微复杂一点,需要配置拦截器和处理器,还需要把它放入到Springboot的启动容器里面,也…

在word文档里面插入漂亮的伪代码

推荐用texsword.0.8 安装与界面 下载链接&#xff1a;https://sourceforge.net/projects/texsword/ 极为轻便&#xff0c;是Word的一个宏 安装过程也是极为简单&#xff0c;复制解压后的 texsword.dotm 文件到 C:\Users\{YOUR_USER_NAME}\AppData\Roaming\Microsoft\Word\ST…

gitgitHub

在git中复制CtrlInsert、粘贴CtrlShif 一、用户名和邮箱的配置 查看用户名 &#xff1a;git config user.name 查看密码&#xff1a; git config user.password 查看邮箱&#xff1a;git config user.email 查看配置信息&#xff1a; $ git config --list 修改用户名 git co…

【树】树的直径和重心

目录 一.树的直径 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;思路 &#xff08;3&#xff09;例题 &#xff08;4&#xff09;std(第一小问) 二.树的重心 &#xff08;1&#xff09;介绍 &#xff08;2&#xff09;求重心 &#xff08;3&#xff09;例…

全排列[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定一个不含重复数字的数组nums&#xff0c;返回其所有可能的全排列。你可以按任意顺序返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 示例…

【机器学习-黑马程序员】人工智能、机器学习概述

文章目录 前言一、人工智能概述二、什么是机器学习二、机器学习算法分类三、机器学习开发流程 前言 本专栏文章为观看黑马程序员《python机器学习》所做笔记&#xff0c;课程地址在这。如有侵权&#xff0c;立即删除。 一、人工智能概述 机器学习和人工智能、深度学习的关系 机…

Harbor+Trivy实现镜像漏洞扫描

文章目录 安装HarborTrivyoras下载压缩包解压并安装 离线漏洞库oras方式trivy方式配置离线库 扫描镜像harbor配置扫描镜像 使用命令扫描 安装 Harbor 安装Harbor时&#xff0c;指定--with-trivy参数同时安装trivy组件。 ./install.sh --with-trivyTrivy 下载最新版本的安装…

目标检测算法改进系列之Backbone替换为FocalNet

FocalNet 近些年&#xff0c;Transformers在自然语言处理、图像分类、目标检测和图像分割上均取得了较大的成功&#xff0c;归根结底是自注意力&#xff08;SA &#xff1a;self-attention&#xff09;起到了关键性的作用&#xff0c;因此能够支持输入信息的全局交互。但是由于…

OpenCV项目开发实战--使用最先进的方法“F、B、Alpha Matting”进行图像抠图--提供完整代码

示范 让我们对现实生活中的图像启动 FBA Matting 方法。要应用 FBA Matting 算法,我们首先需要生成一个 trimap(我们稍后会介绍它是什么)。在我们的演示中,我们将使用预训练的DeepLabV3生成分割掩模,其中每个像素属于前景类的概率。之后,我们将使用大量膨胀操作将边界像…