当前位置: 首页 > news >正文

Robot Operating System——深度解析单线程执行器(SingleThreadedExecutor)执行逻辑

大纲

  • 创建SingleThreadedExecutor
  • 新增Node
    • add_node
    • trigger_entity_recollect
      • collect_entities
  • 自旋等待
    • get_next_executable
      • wait_for_work
      • get_next_ready_executable
        • Timer
        • Subscription
        • Service
        • Client
        • Waitable
        • AnyExecutable
    • execute_any_executable
  • 参考资料

在ROS2中,我们设置的各种回调都是在执行器(Executor)中执行的,所以它是整个系统非常核心的组件。
在这里插入图片描述
目前,rclcpp 提供了三种 Executor 类型,它们派生自同一个父类Executor。
在这里插入图片描述

本文我们将借用《Robot Operating System——单线程中启动多个Node》中的例子,将单线程执行器在最上层的使用方法总结如下

  // Create an executor that will be responsible for execution of callbacks for a set of nodes.// With this version, all callbacks will be called from within this thread (the main one).rclcpp::executors::SingleThreadedExecutor exec;rclcpp::NodeOptions options;// Add some nodes to the executor which provide work for the executor during its "spin" function.// An example of available work is executing a subscription callback, or a timer callback.auto server = std::make_shared<composition::Server>(options);exec.add_node(server);// spin will block until work comes in, execute work as it becomes available, and keep blocking.// It will only be interrupted by Ctrl-C.exec.spin();

即:

  1. 创建SingleThreadedExecutor ;
  2. 新增Node;
  3. 自旋等待。

创建SingleThreadedExecutor

SingleThreadedExecutor的构造函数基本就是交给基类rclcpp::Executor的构造函数来实现。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executors/single_threaded_executor.cpp
SingleThreadedExecutor::SingleThreadedExecutor(const rclcpp::ExecutorOptions & options)
: rclcpp::Executor(options) {}

在Executor的构造函数中,我们着重关注成员变量collector_。

Executor::Executor(const rclcpp::ExecutorOptions & options)
: spinning(false),interrupt_guard_condition_(std::make_shared<rclcpp::GuardCondition>(options.context)),shutdown_guard_condition_(std::make_shared<rclcpp::GuardCondition>(options.context)),context_(options.context),notify_waitable_(std::make_shared<rclcpp::executors::ExecutorNotifyWaitable>([this]() {this->entities_need_rebuild_.store(true);})),entities_need_rebuild_(true),collector_(notify_waitable_),wait_set_({}, {}, {}, {}, {}, {}, options.context),current_notify_waitable_(notify_waitable_),impl_(std::make_unique<rclcpp::ExecutorImplementation>())
{shutdown_callback_handle_ = context_->add_on_shutdown_callback([weak_gc = std::weak_ptr<rclcpp::GuardCondition>{shutdown_guard_condition_}]() {auto strong_gc = weak_gc.lock();if (strong_gc) {strong_gc->trigger();}});notify_waitable_->add_guard_condition(interrupt_guard_condition_);notify_waitable_->add_guard_condition(shutdown_guard_condition_);wait_set_.add_waitable(notify_waitable_);
}

collector_是一个集合,它保存了后续要执行的各个Node指针。

/// Collector used to associate executable entities from nodes and guard conditionsrclcpp::executors::ExecutorEntitiesCollector collector_;

新增Node

add_node

业务逻辑的Node会被添加到上面介绍的成员变量collector_中。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::add_node(rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_ptr, bool notify)
{this->collector_.add_node(node_ptr);try {this->trigger_entity_recollect(notify);} catch (const rclcpp::exceptions::RCLError & ex) {throw std::runtime_error(std::string("Failed to trigger guard condition on node add: ") + ex.what());}
}

然后会调用trigger_entity_recollect方法。

trigger_entity_recollect

这个方法会做两件事:

  1. 修改std::atomic_bool类型变量entities_need_rebuild_的值为true,进而让collect_entities()被执行。
  2. 如果notify为true,则会通过interrupt_guard_condition_->trigger()唤醒一个处于等待状态的执行器。
// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::trigger_entity_recollect(bool notify)
{this->entities_need_rebuild_.store(true);if (!spinning.load() && entities_need_rebuild_.exchange(false)) {std::lock_guard<std::mutex> guard(mutex_);this->collect_entities();}if (notify) {interrupt_guard_condition_->trigger();}
}

collect_entities

collect_entities主要做两件事:

  1. 过滤过期的Node以及相关回调函数。
// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::collect_entities()
{// Updating the entity collection and waitset expires any active resultthis->wait_result_.reset();// Get the current list of available waitables from the collector.rclcpp::executors::ExecutorEntitiesCollection collection;this->collector_.update_collections();auto callback_groups = this->collector_.get_all_callback_groups();rclcpp::executors::build_entities_collection(callback_groups, collection);// We must remove expired entities here, so that we don't continue to use older entities.// See https://github.com/ros2/rclcpp/issues/2180 for more information.current_collection_.remove_expired_entities();
  1. 更新current_collection_和wait_set_。
  // Update each of the groups of entities in the current collection, adding or removing// from the wait set as necessary.current_collection_.timers.update(collection.timers,[this](auto timer) {wait_set_.add_timer(timer);},[this](auto timer) {wait_set_.remove_timer(timer);});current_collection_.subscriptions.update(collection.subscriptions,[this](auto subscription) {wait_set_.add_subscription(subscription, kDefaultSubscriptionMask);},[this](auto subscription) {wait_set_.remove_subscription(subscription, kDefaultSubscriptionMask);});current_collection_.clients.update(collection.clients,[this](auto client) {wait_set_.add_client(client);},[this](auto client) {wait_set_.remove_client(client);});current_collection_.services.update(collection.services,[this](auto service) {wait_set_.add_service(service);},[this](auto service) {wait_set_.remove_service(service);});current_collection_.guard_conditions.update(collection.guard_conditions,[this](auto guard_condition) {wait_set_.add_guard_condition(guard_condition);},[this](auto guard_condition) {wait_set_.remove_guard_condition(guard_condition);});current_collection_.waitables.update(collection.waitables,[this](auto waitable) {wait_set_.add_waitable(waitable);},[this](auto waitable) {wait_set_.remove_waitable(waitable);});// In the case that an entity already has an expired weak pointer// before being removed from the waitset, additionally prune the waitset.this->wait_set_.prune_deleted_entities();
}

自旋等待

spin()内部核心是一个while循环。它会不停使用get_next_executable取出可以运行的Node的回调,然后让execute_any_executable将其执行。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executors/single_threaded_executor.cpp
void
SingleThreadedExecutor::spin()
{if (spinning.exchange(true)) {throw std::runtime_error("spin() called while already spinning");}RCPPUTILS_SCOPE_EXIT(wait_result_.reset();this->spinning.store(false););// Clear any previous result and rebuild the waitsetthis->wait_result_.reset();this->entities_need_rebuild_ = true;while (rclcpp::ok(this->context_) && spinning.load()) {rclcpp::AnyExecutable any_executable;if (get_next_executable(any_executable)) {execute_any_executable(any_executable);}}
}

那么这个while循环会不会导致CPU一直空转呢?答案是:不是。我们可以看get_next_executable的实现。

get_next_executable

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
bool
Executor::get_next_executable(AnyExecutable & any_executable, std::chrono::nanoseconds timeout)
{bool success = false;// Check to see if there are any subscriptions or timers needing service// TODO(wjwwood): improve run to run efficiency of this functionsuccess = get_next_ready_executable(any_executable);// If there are noneif (!success) {// Wait for subscriptions or timers to work onwait_for_work(timeout);if (!spinning.load()) {return false;}// Try againsuccess = get_next_ready_executable(any_executable);}return success;
}

它会在底层调用wait_for_work。

wait_for_work

这个方法会一直阻塞到时间超时,或者有回调函数可以被调用。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::wait_for_work(std::chrono::nanoseconds timeout)
{TRACETOOLS_TRACEPOINT(rclcpp_executor_wait_for_work, timeout.count());// Clear any previous wait resultthis->wait_result_.reset();{std::lock_guard<std::mutex> guard(mutex_);if (this->entities_need_rebuild_.exchange(false) || current_collection_.empty()) {this->collect_entities();}}this->wait_result_.emplace(wait_set_.wait(timeout));if (!this->wait_result_ || this->wait_result_->kind() == WaitResultKind::Empty) {RCUTILS_LOG_WARN_NAMED("rclcpp","empty wait set received in wait(). This should never happen.");} else {if (this->wait_result_->kind() == WaitResultKind::Ready && current_notify_waitable_) {auto & rcl_wait_set = this->wait_result_->get_wait_set().get_rcl_wait_set();if (current_notify_waitable_->is_ready(rcl_wait_set)) {current_notify_waitable_->execute(current_notify_waitable_->take_data());}}}
}

其中主要负责等待的是这句

wait_set_.wait(timeout)

在SingleThreadedExecutor中,由于调用get_next_executable没有传递时间,便采用了默认时间。这样get_next_executable会一直等到有回调可以被执行。这样就避免了CPU空转的问题。

  /// Wait for executable in ready state and populate union structure./*** If an executable is ready, it will return immediately, otherwise* block based on the timeout for work to become ready.** \param[out] any_executable populated union structure of ready executable* \param[in] timeout duration of time to wait for work, a negative value*   (the defualt behavior), will make this function block indefinitely* \return true if an executable was ready and any_executable was populated,*   otherwise false*/RCLCPP_PUBLICboolget_next_executable(AnyExecutable & any_executable,std::chrono::nanoseconds timeout = std::chrono::nanoseconds(-1));

get_next_ready_executable

get_next_ready_executable会按顺序寻找Timer、Subscription、Service 、Client和Waitable中第一个处于可被回调状态的Node。

Timer
bool valid_executable = false;if (!wait_result_.has_value() || wait_result_->kind() != rclcpp::WaitResultKind::Ready) {return false;}if (!valid_executable) {size_t current_timer_index = 0;while (true) {auto [timer, timer_index] = wait_result_->peek_next_ready_timer(current_timer_index);if (nullptr == timer) {break;}current_timer_index = timer_index;auto entity_iter = current_collection_.timers.find(timer->get_timer_handle().get());if (entity_iter != current_collection_.timers.end()) {auto callback_group = entity_iter->second.callback_group.lock();if (!callback_group || !callback_group->can_be_taken_from()) {current_timer_index++;continue;}// At this point the timer is either ready for execution or was perhaps// it was canceled, based on the result of call(), but either way it// should not be checked again from peek_next_ready_timer(), so clear// it from the wait result.wait_result_->clear_timer_with_index(current_timer_index);// Check that the timer should be called still, i.e. it wasn't canceled.any_executable.data = timer->call();if (!any_executable.data) {current_timer_index++;continue;}any_executable.timer = timer;any_executable.callback_group = callback_group;valid_executable = true;break;}current_timer_index++;}}
Subscription
  if (!valid_executable) {while (auto subscription = wait_result_->next_ready_subscription()) {auto entity_iter = current_collection_.subscriptions.find(subscription->get_subscription_handle().get());if (entity_iter != current_collection_.subscriptions.end()) {auto callback_group = entity_iter->second.callback_group.lock();if (!callback_group || !callback_group->can_be_taken_from()) {continue;}any_executable.subscription = subscription;any_executable.callback_group = callback_group;valid_executable = true;break;}}}
Service
  if (!valid_executable) {while (auto service = wait_result_->next_ready_service()) {auto entity_iter = current_collection_.services.find(service->get_service_handle().get());if (entity_iter != current_collection_.services.end()) {auto callback_group = entity_iter->second.callback_group.lock();if (!callback_group || !callback_group->can_be_taken_from()) {continue;}any_executable.service = service;any_executable.callback_group = callback_group;valid_executable = true;break;}}}
Client
  if (!valid_executable) {while (auto client = wait_result_->next_ready_client()) {auto entity_iter = current_collection_.clients.find(client->get_client_handle().get());if (entity_iter != current_collection_.clients.end()) {auto callback_group = entity_iter->second.callback_group.lock();if (!callback_group || !callback_group->can_be_taken_from()) {continue;}any_executable.client = client;any_executable.callback_group = callback_group;valid_executable = true;break;}}}
Waitable
  if (!valid_executable) {while (auto waitable = wait_result_->next_ready_waitable()) {auto entity_iter = current_collection_.waitables.find(waitable.get());if (entity_iter != current_collection_.waitables.end()) {auto callback_group = entity_iter->second.callback_group.lock();if (!callback_group || !callback_group->can_be_taken_from()) {continue;}any_executable.waitable = waitable;any_executable.callback_group = callback_group;any_executable.data = waitable->take_data();valid_executable = true;break;}}}
AnyExecutable
// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/include/rclcpp/any_executable.hpp
struct AnyExecutable
{RCLCPP_PUBLICAnyExecutable();RCLCPP_PUBLICvirtual ~AnyExecutable();// Only one of the following pointers will be set.rclcpp::SubscriptionBase::SharedPtr subscription;rclcpp::TimerBase::SharedPtr timer;rclcpp::ServiceBase::SharedPtr service;rclcpp::ClientBase::SharedPtr client;rclcpp::Waitable::SharedPtr waitable;// These are used to keep the scope on the containing itemsrclcpp::CallbackGroup::SharedPtr callback_group {nullptr};rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base {nullptr};std::shared_ptr<void> data {nullptr};
};

execute_any_executable

找到可以执行的Node后,便可以调用execute_any_executable让其执行。

在execute_any_executable内部,我们看到它也是区分Timer、Subscription、Service 、Client和Waitable类型来执行的。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/executor.cpp
void
Executor::execute_any_executable(AnyExecutable & any_exec)
{if (!spinning.load()) {return;}assert((void("cannot execute an AnyExecutable without a valid callback group"),any_exec.callback_group));if (any_exec.timer) {TRACETOOLS_TRACEPOINT(rclcpp_executor_execute,static_cast<const void *>(any_exec.timer->get_timer_handle().get()));execute_timer(any_exec.timer, any_exec.data);}if (any_exec.subscription) {TRACETOOLS_TRACEPOINT(rclcpp_executor_execute,static_cast<const void *>(any_exec.subscription->get_subscription_handle().get()));execute_subscription(any_exec.subscription);}if (any_exec.service) {execute_service(any_exec.service);}if (any_exec.client) {execute_client(any_exec.client);}if (any_exec.waitable) {const std::shared_ptr<void> & const_data = any_exec.data;any_exec.waitable->execute(const_data);}// Reset the callback_group, regardless of typeany_exec.callback_group->can_be_taken_from().store(true);
}

参考资料

  • https://docs.ros.org/en/rolling/Concepts/Intermediate/About-Executors.html#executors

相关文章:

Robot Operating System——深度解析单线程执行器(SingleThreadedExecutor)执行逻辑

大纲 创建SingleThreadedExecutor新增Nodeadd_nodetrigger_entity_recollectcollect_entities 自旋等待get_next_executablewait_for_workget_next_ready_executableTimerSubscriptionServiceClientWaitableAnyExecutable execute_any_executable 参考资料 在ROS2中&#xff0c…...

【TS】使用npm全局安装typescript

查看npm安装 npm -v 安装typescript npm i -g typescript 查看安装 tsc 这就是标致着安装完成。...

安全用户角色权限

$PATH 搞系统设置设置⾥头path ⽬标包含mysql 可执⾏⽂件&#xff0c;那么就是由使⽤ 在终端使⽤ ./bin/mysql -h192.168.71.164 -P3306 -uroot -proot 1.远程登录前提条件是mysql.user表中的host属性为%&#xff0c;如果是 localhost就不允许远程登录&#xff0c;update…...

代理模式学习

代理模式 代理模式是常用的java设计模式&#xff0c;他的特征是代理类与委托类有同样的接口&#xff0c;代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类&#xff0c;以及事后处理消息等。代理类与委托类之间通常会存在关联关系&#xff0c;一个代理类的对象…...

深入理解Go 语言信号量 Semaphore

1. 什么是信号量 信号量的概念是荷兰计算机科学家 1.1 P/V 操作 Dijkstra 在他的论文中为信号量定义了两个操作 : P 和 V 。 1.2 信号量和互斥锁的区别与联系 信号量有两种类型&#xff1a;二元信号量和计数信号量。 2. 信号量的 channel 实现 程序在运行时&#xff0c;…...

VisualStudio2019下载与安装

1.下载 通过百度网盘分享的文件&#xff1a;VisualStudio2019 链接&#xff1a;https://pan.baidu.com/s/16tqm0ZsOkmXTfGmi4LnGbA 提取码&#xff1a;wx60 --来自百度网盘超级会员V3的分享 2.安装...

李宏毅老师机器学习常见英语词汇

目录 1.Regression &#xff1a;回归2.Classification&#xff1a;分类3.local minima:局部最小值4.saddle point:鞍点5.ground truth:它是机器学习算法的参考标准&#xff0c;用于衡量模型的性的和判断模型的准确性6.optimization:优化 1.Regression &#xff1a;回归 2.Clas…...

人工智能时代,程序员如何保持核心竞争力?

人工智能时代&#xff0c;程序员如何保持核心竞争力&#xff1f; 随着AIGC&#xff08;如chatgpt、midjourney、claude等&#xff09;大语言模型接二连三的涌现&#xff0c;AI辅助编程工具日益普及&#xff0c;程序员的工作方式正在发生深刻变革。有人担心AI可能取代部分编程工…...

WiFi to Ethernet: 树莓派共享无线连接至有线网口,自动通过Captive Poartal网页登录认证

物联网开发系列&#xff1a;物联网开发之旅① WiFi to Ethernet: 树莓派共享无线连接至有线网口&#xff0c;自动通过Captive Poartal验证物联网开发番外篇之 Captive Portal验证原理 文章目录 背景实现工具实现细节一、将无线连接共享到以太网1. 配置静态IP地址2. 启用IP转发3…...

【神软大数据治理平台-高级动态SQL(接口开发)】

1、背景 业务部门需大数据平台按照所提需求提供企业数据接口&#xff0c;基于神软大数据治理平台-高级动态SQL功能&#xff0c;满足业务需求&#xff0c;如下&#xff1a; &#xff08;1&#xff09;业务系统需求&#xff1a; 输入&#xff1a; enterpriseName&#xff1a;…...

【Java数据结构】Map和Set超详细两万字讲解(内含搜索树+哈希表)

&#x1f512;文章目录&#xff1a; 1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; 2. Map和Set的基础概念 3.Map的基础使用 4.Set的基础使用 5. TreeMap的本质——红黑树 5.1二叉搜索树的概念 5.2二叉搜索树的模拟实现 二叉搜索树——查找 二…...

中国制造2025,会抛弃精益生产吗?

时至今日&#xff0c;“精益生产”模式依旧大行其道&#xff0c;它始终支持着中国制造业以最低的成本做出优质产品。我们认为&#xff0c;纵然是中国制造2025成为现实&#xff0c;精益生产模式也仍然是整个制造业的精髓之一。 首先&#xff0c;精益生产模式最重要的一根脊梁就是…...

Rust 循环

Rust 循环 在编程语言中&#xff0c;循环是一种重要的控制结构&#xff0c;它允许我们重复执行一段代码直到满足特定的条件。Rust 语言提供了多种循环方式&#xff0c;每种方式都有其特定的用途和语法。本文将详细介绍 Rust 中的循环&#xff0c;包括 loop、while、while let、…...

数据结构(其四)--特殊矩阵的存储

目录 11.特殊矩阵的压缩存储 &#xff08;1&#xff09;.一维数组的储存结构 &#xff08;2&#xff09;.二维数组的存储结构 &#xff08;3&#xff09;.普通矩阵的存储 &#xff08;4&#xff09;.特殊矩阵的压缩存储 i.对称矩阵 ii.三角矩阵 iii.三对角矩阵 iiii.稀疏矩…...

系统化学习 H264视频编码(06)哥伦布编码

说明&#xff1a;我们参考黄金圈学习法&#xff08;什么是黄金圈法则?->模型 黄金圈法则&#xff0c;本文使用&#xff1a;why-what&#xff09;来学习音H264视频编码。本系列文章侧重于理解视频编码的知识体系和实践方法&#xff0c;理论方面会更多地讲清楚 音视频中概念的…...

手机在网状态接口如何对接?(一)

一、什么是手机在网状态&#xff1f; 传入手机号码&#xff0c;查询该手机号的在网状态&#xff0c;返回内容有正常使用、停机、在网但不可用、不在网&#xff08;销号/未启用/异常&#xff09;、预销户等多种状态。 二、手机在网状态使用场景&#xff1f; 1.信贷审核&#…...

数据结构链表2(常考习题1)(C语言)

移除链表元素&#xff1a; . - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 解题思路&#xff1a; 情况1&#xff1a; 情…...

Rust的运行时多态

Rust的运行时多态 Rust的静态多态即编译时多态&#xff0c;通过**泛型特征约束&#xff08;Generic Type Trait Constrait&#xff09;**来实现&#xff1b; 那么动态多态&#xff08;运行时多态&#xff09;呢&#xff1f;答案是特征对象&#xff08;Trait Object&#xff…...

sqllabs通关

sqllabs5:(报错注入) ?id1 回显You are in........... ?id2-1 回显You are in........... ?id1 回显 1 LIMIT 0,1 判断是字符型&#xff0c;闭合。?id1order by 3-- //页面显示正常我们试了4行得出是报错注入 我们先爆库名 http://127.0.0.1/sqli-labs-master/L…...

RTSP系列四:RTSP Server/Client实战项目

RTSP系列&#xff1a; RTSP系列一&#xff1a;RTSP协议介绍-CSDN博客 RTSP系列二&#xff1a;RTSP协议鉴权-CSDN博客 RTSP系列三&#xff1a;RTP协议介绍-CSDN博客 RTSP系列四&#xff1a;RTSP Server/Client实战项目-CSDN博客 目录 一、RTSP Server实战项目 1、准备 2、…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

大语言模型如何处理长文本?常用文本分割技术详解

为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

WebRTC从入门到实践 - 零基础教程

WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC&#xff1f; WebRTC&#xff08;Web Real-Time Communication&#xff09;是一个支持网页浏览器进行实时语音…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

【WebSocket】SpringBoot项目中使用WebSocket

1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖&#xff0c;添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...