FreeRTOS信号量

news2024/12/19 16:52:20

前面介绍过,队列(queue)可以用于传输数据:在任务之间,任务和中断之间。

消息队列用于传输多个数据,但是有时候我们只需要传递一个状态,这个状态值需要用一个数值表示,比如:

  • 卖家:做好了1个包子,做好了2个包子,做好了3个包子!

  • 买家:买了一个包子,包子数量减1

  • 这个停车位我占了,停车位减1

  • 我开车走了,停车位加1

在这些情况下,我们只需要维护一个数值,使用信号量效率更高,更节省内存。

FreeRTOS信号量简介

信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。其中,“同步”指的是任务之间的同步,即信号量可以使得一个任务等待另一个任务完成某件事后,才继续执行;而“有序访问”指的是对被多任务或中断访问的共享资源(如全局变量)的管理,当一个任务在访问(读取或写入)一个共享资源时,信号量可以防止其他任务或中断在这期间访问(读取或写入)这个共享资源。

FreeRTOS二值信号量

前面说过,信号量是基于队列实现的,二值信号量也不例外,二值信号量实际上就是一个队列长度为1的队列,在这种情况下,队列就只有空和满两种情况,这就是二值信号量,二值信号量通常用于互斥访问或任务同步,与互斥信号量类似,但是二值信号量有优先级反转的问题:优先级反转就是指,当一个高优先级任务因获取一个低优先级任务获取而处于没有资源状态的二值信号量时,这个高优先级的任务就将被阻塞,直到低优先级任务释放二值信号量,在此之前,如果有一个优先级介于高优先级和低优先级之间的中等优先级任务就绪,那么这个中等优先级任务就会抢占低优先级任务,那么,这三个任务中,高优先级任务反而要最后执行,这就是二值信号量带来的优先级反转问题。

FreeRTOS计数型信号量

计数型信号量和二值信号量类似,二值信号量相当于队列长度为1 的队列,因此二值信号量只能容纳一个资源,这也是为什么命名为二值信号量,而计数型信号量相当于队列长度大于0的队列,因此计数型信号零能容纳多个资源,这是在计数型信号量被创建的时候确定的。

计数型信号量适用于下面两种场合:

  1. 事件计数

在这种场合下,每次事件发生后,在事件处理函数中释放计数型信号量(计数型信号量的资源数加1),其他等待事件发生的任务 获取计数型信号量(计数型信号量的资源数减1),这么以来等待事件发生的任务就可以在成功获取到计数型信号量之后执行相应的操作,在这种场合下,计数型信号量的资源数一般在创建时设置为0。

  1. 资源管理

在这种场合下,计数型信号量的资源数代表着共享资源的可用数量,例如前面举例中的停车场中的空车位。一个任务想要访问共享资源,就必须先获取这个共享资源的计数型信号量,之后在成功获取了计数型信号量之后,才可以对这个共享资源进行操作,当然,在使用完共享资源之后也要释放这个共享资源的计数型信号量。在这种场合下,计数型信号量的资源数一般在创建时设置为受其管理的共享资源的最大可用数量。

信号量的特性

信号量的常规操作

信号量这个名字起的很恰当:

  • 信号:起通知作用

  • 量:还可以表示资源的数量

  • 当“量”没有限制时,它就是“计数型信号量”(Counting Semaphores)

  • 当“量”只有0、1两个取值时,它就是“二值信号量”(Binary Seamphores)

  • 支持的动作:“give”给出资源,计数值加1,“take”获得资源,计数值减1

计数信号量的典型场景是:

  • 事件计数:事件产生时“give"信号量,让计数值加1;处理事件时要先”take“信号量,也就是获得信号量,让计数值减1

  • 资源管理:要想访问资源需要先”take“信号量,让计数值减1,用完资源后”give“信号量,让计数值加1

信号量的”give“、”take“双方并不需要相同,可以用于生产者-消费者场合;

  • 生产者为任务A,B,消费者为任务C,D

  • 一开始信号量的计数值为0,如果任务C,D想获得信号量,会有两种结果:

  • 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)

  • 即可返回失败:不等

  • 任务A,B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒

  • 唤醒谁?谁优先级高就唤醒谁,如果大家优先级都一样,就唤醒等待时间最长的人

二值信号量和计数型信号量的唯一差别:就是计数值的最大值被限定为1

信号量跟队列的对比

队列

信号量

可以容纳多个数据,创建队列时有2部分内存:队列结构体、存储数据的空间

只有计数值,无法容纳其他数据

创建信号量时,只需要分配信号量结构体

生产者:没有空间存入数据时可以阻塞

生产者:用于不阻塞,计数值已经达到最大值时返回失败

消费者:没有数据时可以阻塞

消费者:没有资源时可以阻塞

两种信号量对比

二进制信号量

计数型信号量

被创建时初始值为0

被创建时初始值可以设定

其他操作是一样的

其他操作是一样的

二值信号量

二值信号量的本质是一个队列长度为1的队列,该队列就只有空和满两种情况,这就是二值。

二值信号量通常用于互斥访问或任务同步,与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题,所以二值信号量更适合用于同步。

二值信号量相关API函数

使用二值信号量的过程:创建二值信号量->释放二值信号量->获取二值信号量

使用信号量时,先创建,然后去添加资源、获得资源,使用句柄来表示一个信号量

函数

描述

xSemaphoreCreateBinary()

使用动态方式创建二值信号量

xSemaphoreCreateBinaryStatic()

使用静态方式创建二值信号量

xSemaphoreGive()

释放二值信号量

xSemaphoreGiveFromISR()

在中断中释放信号量

xSemaphoreTake()

获取信号量

xSemaphoreTakeFromISR()

在中断中获取信号量

这些函数其实都可以在semphr.h头文件中找到,它们都是一些宏定义,本质上用的其实都是Queue的函数

创建二值信号量

/*
创建一个二进制信号量,返回它的句柄
此函数内部会分配信号量结构体
返回值:返回句柄,非NULL表示成功

可以看出,动态创建二值信号量实际上是调用了函数xQueueGenericCreate()
创建了一个队列长度为1且队列项目大小为信号量队列项目大小的二值信号量类型队列
*/
#define  xSemaphoreCreateBinary()   
         xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

/*
创建一个二进制信号量,返回它的句柄
此函数无需动态分配内存,所以需要现有一个StaticSemaphore_t结构体,并传入它的指针
返回值:返回句柄,非NULL表示成功

从 上 面 的 代 码 中 可 以 看 出 , 函 数 xSemaphoreCreateStatic() 实 际 上 是 调 用 了 函 数
xQueueGenericCreateStatic()创建了一个队列长度为 1 且队列项目大小为信号量队列项目大小的
二值信号量类型队列,需要用户手动分配提供创建二值信号量的内存
*/
#define xSemaphoreCreateBinaryStatic( pxStaticSemaphore )   
         xQueueGenericCreateStatic( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticSemaphore, queueQUEUE_TYPE_BINARY_SEMAPHORE )

获取信号量

/*
此函数用于获取信号量,如果信号量处于没有资源的状态,那么此函数可以选择将任务进
行阻塞,如果成功获取了信号量,那信号量的资源数将会减 1。该函数实际上是一个宏定义
*/
#define xSemaphoreTake( xSemaphore, xBlockTime )    
    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
                                TickType_t xTicksToWait )
{
    BaseType_t xEntryTimeSet = pdFALSE;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    #if ( configUSE_MUTEXES == 1 )
        BaseType_t xInheritanceOccurred = pdFALSE;
    #endif

    /* Check the queue pointer is not NULL. */
    configASSERT( ( pxQueue ) );

    /* 信号量类型队列的项目大小为0 */
    configASSERT( pxQueue->uxItemSize == 0 );

    /* Cannot block if the scheduler is suspended. */
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    /*lint -save -e904 This function relaxes the coding standard somewhat to allow return
     * statements within the function itself.  This is done in the interest
     * of execution time efficiency. */
    for( ; ; )
    {    /*进入临界区*/
        taskENTER_CRITICAL();
        {
            /* 获取信号量的资源数 */
            const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;

            /* 判断信号量是否有资源 */
            if( uxSemaphoreCount > ( UBaseType_t ) 0 )
            {
                traceQUEUE_RECEIVE( pxQueue );

                /* 更新信号量的资源数 */
                pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;

                #if ( configUSE_MUTEXES == 1 )
                    {
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                        {
                            /* 设置互斥信号量的持有者并更新互斥信号量的持有次数 */
                            pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* configUSE_MUTEXES */

                /* 判断信号量的获取阻塞任务列表中是否有任务 */
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    /*将阻塞任务从信号量获取阻塞任务列表中移除*/
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        /*根据需要进行任务切换*/
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                    else
                    {
                        mtCOVERAGE_TEST_MARKER();
                    }
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }

                taskEXIT_CRITICAL();
                return pdPASS;
            }
            /*信号量没有资源*/
            else
            {
                /*判断是否不选择阻塞等待信号量*/
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                    /* 此宏用于启用互斥信号量 */
                    #if ( configUSE_MUTEXES == 1 )
                        {
                            configASSERT( xInheritanceOccurred == pdFALSE );
                        }
                    #endif /* configUSE_MUTEXES */

                    /* 退出临界区*/
                    taskEXIT_CRITICAL();
                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    return errQUEUE_EMPTY;
                }
                /*选择阻塞等待信号量*/
                else if( xEntryTimeSet == pdFALSE )
                {
                    /* 队列满,任务需要阻塞
                        记录此时系统节拍计数器 值和溢出次数
                        用于下面对阻塞时间进行补偿
                     */
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
                else
                {
                    /* Entry time was already set. */
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
        taskEXIT_CRITICAL();

        /* 挂起任务调度器 */

        vTaskSuspendAll();
        /*信号量队列上锁*/
        prvLockQueue( pxQueue );

        /* 判断阻塞时间补偿后,是否还需要阻塞 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
            /* 判断队列是否为空 */
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );

                #if ( configUSE_MUTEXES == 1 )
                    {
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                        {
                            taskENTER_CRITICAL();
                            {
                                /*进行优先级继承,这是互斥信号量用于解决优先级翻转问题的*/
                                xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
                            }
                            taskEXIT_CRITICAL();
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }
                    }
                #endif /* if ( configUSE_MUTEXES == 1 ) */
                /*将任务添加到队列写入阻塞任务列表中进行阻塞*/
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
                prvUnlockQueue( pxQueue );
                /*恢复任务调度器*/
                if( xTaskResumeAll() == pdFALSE )
                {
                    portYIELD_WITHIN_API();
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            /*队列不为空*/
            else
            {
                /* There was no timeout and the semaphore count was not 0, so
                 * attempt to take the semaphore again. */
                prvUnlockQueue( pxQueue );
                ( void ) xTaskResumeAll();
            }
        }
        else
        {
            /* Timed out. */
            prvUnlockQueue( pxQueue );
            ( void ) xTaskResumeAll();

            /* If the semaphore count is 0 exit now as the timeout has
             * expired.  Otherwise return to attempt to take the semaphore that is
             * known to be available.  As semaphores are implemented by queues the
             * queue being empty is equivalent to the semaphore count being 0. */
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                #if ( configUSE_MUTEXES == 1 )
                    {
                        /* xInheritanceOccurred could only have be set if
                         * pxQueue->uxQueueType == queueQUEUE_IS_MUTEX so no need to
                         * test the mutex type again to check it is actually a mutex. */
                        if( xInheritanceOccurred != pdFALSE )
                        {
                            taskENTER_CRITICAL();
                            {
                                UBaseType_t uxHighestWaitingPriority;

                                /* This task blocking on the mutex caused another
                                 * task to inherit this task's priority.  Now this task
                                 * has timed out the priority should be disinherited
                                 * again, but only as low as the next highest priority
                                 * task that is waiting for the same mutex. */
                                uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
                                vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );
                            }
                            taskEXIT_CRITICAL();
                        }
                    }
                #endif /* configUSE_MUTEXES */

                traceQUEUE_RECEIVE_FAILED( pxQueue );
                return errQUEUE_EMPTY;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
    } /*lint -restore */
}

释放信号量

#define xSemaphoreGive( xSemaphore )    
    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

这里其实和上面的获取是类似的,碍于篇幅有限,不给出源码。

在任务中使用

在ISR中使用

give

xSemaphoreGive

xSemaphoreGiveFromISR

take

xSemaphoreTake

xSemaphoreTakeFromISR

删除

对于动态创建的信号量,不再需要使用它们时,可以删除它们以回收内存

vSemaphoreDelete可以删除二值信号量,计数型信号量,函数原型如下:

#define vSemaphoreDelete( xSemaphore )               
    vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )

计数型信号量

计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定。

计数型信号量相关API函数

函数

描述

xSemaphoreCreateCounting()

使用动态方法创建计数型信号量

xSemaphoreCreateCountingStatic()

使用静态方法创建计数型信号量

uxSemaphoreGetCount()

获取信号量的计数值

计数值信号量的获取和释放和二值信号量的相同,这里不再赘述。

计数型信号量创建

/*
uxMaxCount:计数值的最大值限定
uxInitialCount:计数值的初始值
NULL:创建失败
其他值:创建成功返回计数型信号量的句柄
*/ 
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )   
         xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )

获取信号量当前计数值大小

/*
xSemaphore:信号量句柄
返回值:整数,当前信号量的计数值大小
*/
#define uxSemaphoreGetCount( xSemaphore )             
   uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )

个人总结

  • 信号量是一种特殊的队列,其中二值信号量是长度为1的队列,计数型信号量是长度大于1的队列

  • 使用信号量,只是传递一个状态,而不是像队列一样可以缓存数据,然后进行数据交流,信号量相比于队列更加轻量化,只需要创建信号量结构体,队列不仅需要申请创建队列的结构体还包括存储数据的缓存区。

  • 二值信号量其实是一种特殊的计数型信号量,只有0和1两个计数值,常常用于互斥访问和任务间同步,但是二值信号量会导致优先级翻转的问题,互斥量被引申出解决该问题,所以二值信号量最合适用于任务同步。

  • 其实,不管是信号量,队列,互斥量,事件组等IPC(在线程通信)都是这样,都要先创建一个中间结构体,任务之间通过该结构体传输信息,进行交流,这是一种间接的任务之间通信方式。

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

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

相关文章

Android运行时权限Runtime Permission源码分析

Runtime Permission源码跟踪 Android 8.1.0 请求权限时弹窗代码 应用使用requestPermissions申请权限时,系统会弹出一个选择窗口,可进行允许。 源码在packages/apps/PackageInstaller/文件下 GrantPermissionsActivity.java是进行权限分配的弹出窗口…

分布式之PBFT算法

写在前面 在分布式之拜占庭问题 一文中我们分析了拜占庭问题,并一起看了支持拜占庭容错的口信消息性和签名消息性算法,但是这两种算法都有一个非常严重的问题,就是消息数量太多,通信的成本太大,消息数量复杂度为O(n ^…

CentOS 环境 OpneSIPS 3.1 版本安装及使用

文章目录1. OpenSIPS 源码下载2. 工具准备3. 编译安装4. opensips-cli 工具安装5. 启动 OpenSIPS 实例1. OpenSIPS 源码下载 使用以下命令即可下载 OpenSIPS 的源码,笔者下载的是比较稳定的 3.1 版本,读者有兴趣也可前往 官方传送门 sudo git clone htt…

1个串口用1根线实现多机半双工通信+开机控制电路

功能需求: 主机使用一个串口,与两个从机进行双向通信,主机向从机发送数据,从机能够返回数据,由于结构限制,主机与从机之间只有3根线(电源、地、数据线),并且从机上没有设…

【蓝牙mesh】access层(接入层)协议介绍

【蓝牙mesh】access层(接入层)协议介绍 Access层简介 Access层定义了应用层如何使用upper协议层的接口,它不仅定义了应用层的格式,还定义了应用数据在upper层的加密和解密。当收到下层的数据包时,它会检查数据的netke…

Web前端:选择AngularJS进行Web开发的12大理由!

无论你希望构建本地应用程序、渐进式web应用程序(PWA),还是开发单页应用程序,每个企业都寻求具有影响力的数字形象,并希望构建交互式、跨平台和动态的web应用程序,以吸引客户。AngularJS,也称为Angular,是一…

李宏毅2023春季机器学习课程

目录2021&2022课程重磅须知我维护的其他项目更新日志课程地址课程资料直链课程作业直链其他优质课程2021&2022课程 CSDN Github 重磅须知 为方便所有网课资料与优质电子书籍的实时更新维护,创建一个在线实时网盘文件夹;   网盘获取方式&#…

C++进阶——继承

C进阶——继承 1.继承的概念及定义 面向对象三大特性:封装、继承、多态。 概念: 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加功能,这…

MySql数据库环境部署

MySql基础与Sql数据库概述基础环境的建立MYSQL数据库的连接方法MySql的默认数据库数据库端口号数据库概述 数据库(DataBase,DB)∶存储在磁带、磁盘、光盘或其他外存介质上、按定结构组织在一起的相关数据的集合。数据库管理系统〈DataBase Management S…

SpringMVC常用注释

1.RequestMapping注释:用来匹配客户端发送的请求,可以在方法上使用,也可以在类上使用。方法:表示用来匹配要处理的请求 类上:表示为当前类的所有方法的请求地址添加一个前置路径,访问的时候必须要添加此路径…

一个.Net Core开源缓存中间件,让你更加简单、方便使用缓存

上次给大家推荐过一个缓存中间件《一个C#开发的非常实用的缓存中间件》,今天再给大家推荐一个缓存中间件,两者功能差不多,都是提供统一接口、多级缓存、分布式缓存、支持多种Provider等。 项目简介 这是一个基于.Net Core开发的缓存中间件&…

Java虚拟机之类加载学习总结

文章目录1 什么是类加载1.1 类加载的应用1.2 类加载过程1.3 类的验证1.4 类初始化顺序2 类加载时机3 类加载器3.1 类加载分类3.2 双亲委派3.3 自定义类加载器3.4 类加载器的命名空间4 打破双亲委派4.1 线程上下文类加载器4.2 自定义类加载器5 类的卸载1 什么是类加载 Java 虚拟…

【工具】JSR-303后端参数校验框架的使用方法及说明

【工具】JSR-303后端参数校验框架的使用方法及说明 文章目录【工具】JSR-303后端参数校验框架的使用方法及说明1. 统一校验需求2. 使用说明2.1 引入依赖2.2 规则说明2.3 使用说明2.4 分组校验2.5 定制校验规则注解1. 统一校验需求 有一句话是这样说的——“前端防君子&#xf…

小知识点:Confluence + mysql 安装流程

流程一、Confluence 配置二、MySQL 配置三、启动一、Confluence 配置 访问下载地址,下载最新安装包 Confluence Server 下载存档 | Atlassian创建环境目录 mkdir -p /xxx/confluence/confluence-home 解压安装包 tar -zxvf atlassian-confluence-7.xx.x.tar.gz -C …

人工智能- windows10环境,配rtx 3060ti显卡,tensorflow-gpu安装

文章目录前言流程方法1.先安装网盘里的anaconda文件,安装后就是python3.8.8环境2.安装vs20193.vs2019安装完毕后开始安装cuda4.安装cudnn5.安装tensorflow-gpu6.测试GPU是否正常识别,tensorflow是否可用前言 最近显卡降价,入手了一块RTX3060…

ROS小车研究笔记:二维SLAM建图简介与源码分析

ROS提供了现成的各类建图算法实现。如果只是应用的话不需要了解详细算法原理,只需要了解其需要的输入输出即可。 1 Gmapping Gmapping使用粒子滤波算法进行建图,在小场景下准确度高,但是在大场地中会导致较大计算量和内存需求 Gmapping需要…

Go语言内存管理详解-学习笔记

1 自动内存管理 1.1 相关概念 Mutator:业务线程,分配新对象,修改对象指向关系Collector:GC线程,找到存活对象,回收死亡对象的内存空间Serial GC:只有一个collector(需要暂停&#…

读书笔记//《数据分析之道》

出版时间:2022年 作者曾在互联网大厂做数据分析。从举例可以洞见作者的工作经历。 点评:作者在数据分析领域非常资深,尝试在书中提供一个数据分析工作框架参考。书本内容有点感觉是ppt的集合,辅以案例说明。不过,干货还…

基于ORB-SLAM2+RTAB-MAP+ROS的三维重建设计——环境配置与安装

写下这篇是为了毕设题目《基于深度相机的电缆识别系统》。使用的设备与环境如下:Ubuntu 20.04ROSGazebo仿真运行Kinect 2.0ORB-SLAM2论文地址:https://arxiv.org/abs/1610.06475GitHub:https://github.com/raulmur/ORB_SLAM2一、为什么要选择…

python多线程网络编程

背景 使用过flask框架后,我对request这个全局实例非常感兴趣。它在客户端发起请求后会保存着所有的客户端数据,例如用户上传的表单或者文件等。那么在很多客户端发起请求时,服务器是怎么去区分不同的request对象呢?当查看了大量的…