ros通信与回调函数多线程应用

news2025/5/19 18:37:05

一、ros topic通信流程

参考资料:
一个ros节点至少有几个线程(1058): https://zhuanlan.zhihu.com/p/680188065
鱼香rostopic底层流程(1005~1008): https://zhuanlan.zhihu.com/p/656716718
王方浩-ROS发布订阅实现(二): https://zhuanlan.zhihu.com/p/439208597

  • 基础的topic通信流程涉及到三个线程:
    ①发布者节点调用publish()函数
    ②pollset线程中处理基于socket的tcp通信和基于文件标识符的事件处理
    ③订阅者节点spin()中处理回调函数队列
  • publish() 函数只是把要发送的消息放到了消息队列中,实际上并没有真的执行tcp通信,这跟普通的基于socket的通信是类似的。这个消息队列的大小由定义发布者时的参数定义,这个队列建立的意义实际上是为了tcp通信建立的,tcp通信需要时间(通信时间和等待系统调度的时间),与订阅者的回调函数队列是不同的。除了将数据放入队列,publish还触发了一个信号poll_signal_,这个信号在pollset中被轮讯监控,一旦出发就调用槽函数processPublishQueues.
  • pollset线程中的槽函数processPublishQueues,就会根据subscrib_link列表依次实现tcp通讯,subscrib_link是一个所有不同tcp连接的线程池。这个列表是节点在调用advertise时注册进去的。topic通信是异步的,所以实质上一个topic如果有两个发布者和三个订阅者,就要建立六条独立的tcp连接。pollset线程除了处理发布的线程,也把接受的消息放进了订阅者的队列中
  • 订阅者正常情况下是一个单线程的程序spin()中是一个循环,不断的监控回调函数队列,有数据时就依次执行。如果用spinonce()的话,就是每次调用时把队列中的数据全部执行一次.
  • 以上就是一个基本流程,具体细节可能不是很清楚,但基本上知道ros自己有单独的线程在处理tcp通信,而节点中的代码只是在处理队列信息就可以了

二、ros 多线程通信

参考资料:
动手学ROS(7):精讲多线程之MultiThreadedSpinner: https://zhuanlan.zhihu.com/p/375418691

  • 代码主要都来自上面的链接
  • ros的多线程主要体现在回调函数队列的处理上

1、设置两个发布者

  • 这两个发布者都以1hz的频率往不同的topic发送消息
  • 发布者A
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "talker_A");
    ros::NodeHandle n;

    // 创建两个发布者对象,分别发布到"A/message"和"B/message"主题
    ros::Publisher pub_a = n.advertise<std_msgs::String>("A/message", 100);

    // 设置发布频率为1kHz
    ros::Rate loop_rate(1);

    int count = 0;
    while (ros::ok())
    {
        // 创建消息对象
        std_msgs::String msg_a;

        // 填充消息内容
        std::stringstream ss_a;
        ss_a << "Hello from A " << count;
        msg_a.data = ss_a.str();


        // 打印发布的信息
        ROS_INFO("%s", msg_a.data.c_str());

        // 发布消息
        pub_a.publish(msg_a);
        // 增加计数器
        count++;

        // 休眠一段时间以达到设定的发布频率
        loop_rate.sleep();
    }

    return 0;
}

  • 发布者B
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc, char **argv)
{
    // 初始化ROS节点
    ros::init(argc, argv, "talker_B");
    ros::NodeHandle n;

    // 创建两个发布者对象,分别发布到"A/message"和"B/message"主题
    ros::Publisher pub_b = n.advertise<std_msgs::String>("B/message", 1000);

    // 设置发布频率为1kHz
    ros::Rate loop_rate(1);

    int count = 0;
    while (ros::ok())
    {
        // 创建消息对象
        std_msgs::String msg_b;

        // 填充消息内容
        std::stringstream ss_b;
        ss_b << "Hello from B " << count;
        msg_b.data = ss_b.str();

        // 发布消息
        pub_b.publish(msg_b);

        // 打印发布的信息
        ROS_INFO("%s", msg_b.data.c_str());

        // 增加计数器
        count++;

        // 休眠一段时间以达到设定的发布频率
        loop_rate.sleep();
    }

    return 0;
}

2、单spin线程

在这里插入图片描述

 #include <thread>
 #include "ros/ros.h"
 #include "std_msgs/String.h"
 void CallbackA(const std_msgs::String::ConstPtr &msg)
 {
     std::this_thread::sleep_for(std::chrono::seconds(2));
     ROS_INFO(" I heard: [%s]", msg->data.c_str());
 }
 void CallbackB(const std_msgs::String::ConstPtr &msg)
 {
     ROS_INFO(" I heard: [%s]", msg->data.c_str());
 }
 int main(int argc, char **argv)
 {
     ros::init(argc, argv, "listener");
     ros::NodeHandle n;
     ros::Subscriber sub_b = n.subscribe("B/message", 1000, CallbackB);

     ros::Subscriber sub_a = n.subscribe("A/message", 1000, CallbackA);
     ros::spin();

     return 0;
 }

(1) 测试1:先启动订阅者,然后启动两个发布者

  • 由于是手动启动,A节点早,所以最开始有多个A
  • 可以看到B被A托慢了,也变成两秒一次了
[ INFO] [1740131084.794161868]:  I heard: [Hello from A 1]
[ INFO] [1740131086.796284945]:  I heard: [Hello from A 2]
[ INFO] [1740131088.796593596]:  I heard: [Hello from A 3]
[ INFO] [1740131088.796690551]:  I heard: [Hello from B 1]
[ INFO] [1740131090.796933583]:  I heard: [Hello from A 4]
[ INFO] [1740131090.797038170]:  I heard: [Hello from B 2]
[ INFO] [1740131092.797252138]:  I heard: [Hello from A 5]
[ INFO] [1740131092.797336626]:  I heard: [Hello from B 3]
[ INFO] [1740131094.797485092]:  I heard: [Hello from A 6]
[ INFO] [1740131094.797575846]:  I heard: [Hello from B 4]
[ INFO] [1740131096.797819265]:  I heard: [Hello from A 7]
[ INFO] [1740131096.797901295]:  I heard: [Hello from B 5]
[ INFO] [1740131098.798104208]:  I heard: [Hello from A 8]
[ INFO] [1740131098.798182412]:  I heard: [Hello from B 6]
[ INFO] [1740131100.798384053]:  I heard: [Hello from A 9]
[ INFO] [1740131100.798458471]:  I heard: [Hello from B 7]

3、多spin线程非并发

在这里插入图片描述

 #include <thread>
 #include "ros/ros.h"
 #include "std_msgs/String.h"
 void CallbackA(const std_msgs::String::ConstPtr &msg)
 {
     std::this_thread::sleep_for(std::chrono::seconds(2));
     ROS_INFO(" I heard: [%s]", msg->data.c_str());
 }
 void CallbackB(const std_msgs::String::ConstPtr &msg)
 {
     ROS_INFO(" I heard: [%s]", msg->data.c_str());
 }
 int main(int argc, char **argv)
 {
     ros::init(argc, argv, "listener");
     ros::NodeHandle n;
     ros::Subscriber sub_a = n.subscribe("A/message", 10, CallbackA);
     ros::Subscriber sub_b = n.subscribe("B/message", 10, CallbackB);

     ros::MultiThreadedSpinner spinner(2);
     spinner.spin();

     return 0;
 }

(2) 测试2:先启动订阅者,然后启动两个发布者

  • 注意这里修改了订阅者队列的长度
  • 可以看到B不再被托慢了,但是在持续一段时间后A会出现丢包,因为A处理的慢,所以队列被A的数据沾满了
  • 这两个线程会区分两个函数,也就是非并发,即不会两个线程都去处理同一个类型的回调函数,所以B一直没有丢包,就是因为一直有一个线程在处理B
[ INFO] [1740131454.639828056]:  I heard: [Hello from B 1]
[ INFO] [1740131454.653981192]:  I heard: [Hello from A 1]
[ INFO] [1740131455.639724860]:  I heard: [Hello from B 2]
[ INFO] [1740131456.639754199]:  I heard: [Hello from B 3]
[ INFO] [1740131456.654096513]:  I heard: [Hello from A 2]
[ INFO] [1740131457.639836399]:  I heard: [Hello from B 4]
[ INFO] [1740131458.639711465]:  I heard: [Hello from B 5]
[ INFO] [1740131458.654176314]:  I heard: [Hello from A 3]
[ INFO] [1740131459.639715444]:  I heard: [Hello from B 6]
[ INFO] [1740131460.639766550]:  I heard: [Hello from B 7]
[ INFO] [1740131460.654252110]:  I heard: [Hello from A 4]
[ INFO] [1740131461.639824670]:  I heard: [Hello from B 8]
[ INFO] [1740131462.639892430]:  I heard: [Hello from B 9]
[ INFO] [1740131462.654330989]:  I heard: [Hello from A 5]
[ INFO] [1740131463.640043684]:  I heard: [Hello from B 10]
[ INFO] [1740131464.639945528]:  I heard: [Hello from B 11]
[ INFO] [1740131464.654449080]:  I heard: [Hello from A 6]
[ INFO] [1740131465.640064511]:  I heard: [Hello from B 12]
[ INFO] [1740131466.639987938]:  I heard: [Hello from B 13]
[ INFO] [1740131466.654532060]:  I heard: [Hello from A 7]
[ INFO] [1740131467.639809394]:  I heard: [Hello from B 14]
[ INFO] [1740131468.639899303]:  I heard: [Hello from B 15]
[ INFO] [1740131468.654613126]:  I heard: [Hello from A 8]
[ INFO] [1740131469.639891146]:  I heard: [Hello from B 16]
[ INFO] [1740131470.639872857]:  I heard: [Hello from B 17]
[ INFO] [1740131470.654735428]:  I heard: [Hello from A 9]
[ INFO] [1740131471.639807625]:  I heard: [Hello from B 18]
[ INFO] [1740131472.639937912]:  I heard: [Hello from B 19]
[ INFO] [1740131472.654906283]:  I heard: [Hello from A 10]
[ INFO] [1740131473.639993581]:  I heard: [Hello from B 20]
[ INFO] [1740131474.639798626]:  I heard: [Hello from B 21]
[ INFO] [1740131474.655083576]:  I heard: [Hello from A 12]
[ INFO] [1740131475.639838516]:  I heard: [Hello from B 22]
[ INFO] [1740131476.639747335]:  I heard: [Hello from B 23]
[ INFO] [1740131476.655254436]:  I heard: [Hello from A 14]
[ INFO] [1740131477.639884596]:  I heard: [Hello from B 24]
[ INFO] [1740131478.639752969]:  I heard: [Hello from B 25]
[ INFO] [1740131478.655428197]:  I heard: [Hello from A 16]
[ INFO] [1740131479.639717125]:  I heard: [Hello from B 26]
[ INFO] [1740131480.639765754]:  I heard: [Hello from B 27]
[ INFO] [1740131480.655604308]:  I heard: [Hello from A 18]
[ INFO] [1740131481.639775598]:  I heard: [Hello from B 28]
[ INFO] [1740131482.639767451]:  I heard: [Hello from B 29]
[ INFO] [1740131482.655720449]:  I heard: [Hello from A 20]
[ INFO] [1740131483.639719414]:  I heard: [Hello from B 30]
[ INFO] [1740131484.639791912]:  I heard: [Hello from B 31]
[ INFO] [1740131484.655802413]:  I heard: [Hello from A 22]
[ INFO] [1740131485.639867103]:  I heard: [Hello from B 32]
[ INFO] [1740131486.639875775]:  I heard: [Hello from B 33]
[ INFO] [1740131486.655971307]:  I heard: [Hello from A 24]
[ INFO] [1740131487.639853388]:  I heard: [Hello from B 34]
[ INFO] [1740131488.639927926]:  I heard: [Hello from B 35]
[ INFO] [1740131488.656069911]:  I heard: [Hello from A 26]
[ INFO] [1740131489.639944256]:  I heard: [Hello from B 36]
[ INFO] [1740131490.639953991]:  I heard: [Hello from B 37]
[ INFO] [1740131490.656157923]:  I heard: [Hello from A 28]
[ INFO] [1740131491.639920782]:  I heard: [Hello from B 38]
[ INFO] [1740131492.639786618]:  I heard: [Hello from B 39]
[ INFO] [1740131492.656243807]:  I heard: [Hello from A 30]
[ INFO] [1740131493.639929171]:  I heard: [Hello from B 40]
[ INFO] [1740131494.639995655]:  I heard: [Hello from B 41]
[ INFO] [1740131494.656327222]:  I heard: [Hello from A 32]
[ INFO] [1740131495.639892878]:  I heard: [Hello from B 42]
[ INFO] [1740131496.639876764]:  I heard: [Hello from B 43]
[ INFO] [1740131496.656411572]:  I heard: [Hello from A 34]
[ INFO] [1740131497.639974678]:  I heard: [Hello from B 44]
[ INFO] [1740131498.639951815]:  I heard: [Hello from B 45]
[ INFO] [1740131498.656500217]:  I heard: [Hello from A 36]
[ INFO] [1740131499.639837483]:  I heard: [Hello from B 46]

4、多spin线程并发

在这里插入图片描述

 #include <thread>
 #include "ros/ros.h"
 #include "std_msgs/String.h"

 void ChatterCallback(const std_msgs::String::ConstPtr& msg) {
   ROS_INFO(" I heard: [%s]", msg->data.c_str());
   std::this_thread::sleep_for(std::chrono::seconds(2));
 }
 int main(int argc, char **argv) {
   ros::init(argc, argv, "listener");
   
   ros::NodeHandle n;
   ros::SubscribeOptions ops;
   ops.init<std_msgs::String>("A/message", 1, ChatterCallback);
   ops.allow_concurrent_callbacks = true;
   ros::Subscriber sub1 = n.subscribe(ops);
   ros::MultiThreadedSpinner spinner(2);
   
   spinner.spin();
   return 0;
 }

(3) 测试3:先启动订阅者,然后启动A发布者

  • 注意这里修改了订阅者队列的长度
  • A不再丢包,因为两个线程可以同时处理同一个类型的回调,注意这里只是同一个类型,不是同一个
  • 注意这里由于涉及到两个线程操作同一个类型的回调,如果回调里有共享资源,需要有锁
  • 上面非并发的实际上如果有共享资源的话也需要
[ INFO] [1740131990.803107129]:  I heard: [Hello from A 1]
[ INFO] [1740131991.802900084]:  I heard: [Hello from A 2]
[ INFO] [1740131992.804808048]:  I heard: [Hello from A 3]
[ INFO] [1740131993.803073730]:  I heard: [Hello from A 4]
[ INFO] [1740131994.804944148]:  I heard: [Hello from A 5]
[ INFO] [1740131995.803246682]:  I heard: [Hello from A 6]
[ INFO] [1740131996.805189157]:  I heard: [Hello from A 7]
[ INFO] [1740131997.803411020]:  I heard: [Hello from A 8]
[ INFO] [1740131998.805365004]:  I heard: [Hello from A 9]
[ INFO] [1740131999.803570627]:  I heard: [Hello from A 10]
[ INFO] [1740132000.805542830]:  I heard: [Hello from A 11]
[ INFO] [1740132001.803732381]:  I heard: [Hello from A 12]
[ INFO] [1740132002.805717474]:  I heard: [Hello from A 13]
[ INFO] [1740132003.803927256]:  I heard: [Hello from A 14]
[ INFO] [1740132004.805870949]:  I heard: [Hello from A 15]
[ INFO] [1740132005.804098520]:  I heard: [Hello from A 16]
[ INFO] [1740132006.806118693]:  I heard: [Hello from A 17]
[ INFO] [1740132007.804269056]:  I heard: [Hello from A 18]
[ INFO] [1740132008.806373739]:  I heard: [Hello from A 19]
[ INFO] [1740132009.804453823]:  I heard: [Hello from A 20]
[ INFO] [1740132010.806567750]:  I heard: [Hello from A 21]
[ INFO] [1740132011.804627770]:  I heard: [Hello from A 22]
[ INFO] [1740132012.806739975]:  I heard: [Hello from A 23]
[ INFO] [1740132013.804808557]:  I heard: [Hello from A 24]

4、多回调函数队列

在这里插入图片描述

 #include <thread>
 #include <ros/callback_queue.h>
 #include "ros/ros.h"
 #include "std_msgs/String.h"
 void CallbackA(const std_msgs::String::ConstPtr& msg) {
   ROS_INFO(" I heard: [%s]", msg->data.c_str());
   std::this_thread::sleep_for(std::chrono::seconds(2));
 }
 void CallbackB(const std_msgs::String::ConstPtr& msg) {
   ROS_INFO(" I heard: [%s]", msg->data.c_str());
 }
 int main(int argc, char **argv) {
   ros::init(argc, argv, "listener");
   ros::NodeHandle n;
   ros::Subscriber sub_b = n.subscribe("B/message", 10, CallbackB);
   
   ros::NodeHandle n_a;
   ros::CallbackQueue callback_queue_a;
   n_a.setCallbackQueue(&callback_queue_a);
   ros::Subscriber sub_a = n_a.subscribe("A/message", 10, CallbackA);
   std::thread spinner_thread_a([&callback_queue_a]() {
     ros::SingleThreadedSpinner spinner_a;
     spinner_a.spin(&callback_queue_a);
   });
   ros::spin();
   spinner_thread_a.join();
   return 0;
 }


(3) 测试3:先启动订阅者,然后启动A,过一段时间开始出现丢包后启动B

  • 这实际上就是用不同的句柄在自己设置的线程里设置发布者了
  • A持续一段时间后,应该填满了回调队列,如果用之前那种就没办法在启动B后立即获得B了,而这种则不受影响,因为有两个独立的回调函数队列
[ INFO] [1740132598.928969904]:  I heard: [Hello from A 1]
[ INFO] [1740132600.930244784]:  I heard: [Hello from A 2]
[ INFO] [1740132602.930408245]:  I heard: [Hello from A 3]
[ INFO] [1740132604.930572266]:  I heard: [Hello from A 4]
[ INFO] [1740132606.930738016]:  I heard: [Hello from A 5]
[ INFO] [1740132608.930896064]:  I heard: [Hello from A 6]
[ INFO] [1740132610.931070513]:  I heard: [Hello from A 7]
[ INFO] [1740132612.931232193]:  I heard: [Hello from A 8]
[ INFO] [1740132614.931404774]:  I heard: [Hello from A 9]
[ INFO] [1740132616.931579713]:  I heard: [Hello from A 10]
[ INFO] [1740132618.931739429]:  I heard: [Hello from A 12]
[ INFO] [1740132620.931897491]:  I heard: [Hello from A 14]
[ INFO] [1740132622.932063793]:  I heard: [Hello from A 16]
[ INFO] [1740132624.932231221]:  I heard: [Hello from A 18]
[ INFO] [1740132626.932386668]:  I heard: [Hello from A 20]
[ INFO] [1740132626.941445056]:  I heard: [Hello from B 1]
[ INFO] [1740132627.941439882]:  I heard: [Hello from B 2]
[ INFO] [1740132628.932554571]:  I heard: [Hello from A 22]
[ INFO] [1740132628.941478266]:  I heard: [Hello from B 3]
[ INFO] [1740132629.941387904]:  I heard: [Hello from B 4]
[ INFO] [1740132630.932677572]:  I heard: [Hello from A 24]
[ INFO] [1740132630.941351494]:  I heard: [Hello from B 5]
[ INFO] [1740132631.941298778]:  I heard: [Hello from B 6]
[ INFO] [1740132632.932823637]:  I heard: [Hello from A 26]
[ INFO] [1740132632.941288302]:  I heard: [Hello from B 7]

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

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

相关文章

智能交通系统(Intelligent Transportation Systems):智慧城市中的交通革新

智能交通系统&#xff08;Intelligent Transportation Systems, ITS&#xff09;是利用先进的信息技术、通信技术、传感技术、计算机技术以及自动化技术等&#xff0c;来提升交通系统效率和安全性的一种交通管理方式。ITS通过收集和分析交通数据&#xff0c;智能化地调度、控制…

基于LM Arena 的 LLM 基准测试排行榜:DeepSeek-R1 排名第 5

打开 Arena 网站&#xff1a;https://lmarena.ai/&#xff0c;点开 Leaderboard 可以看到上图的排行榜&#xff0c;可以看到 DeepSeek-R1 排名第 5。

【QT】第一个 QT程序(对象树)

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Qt 目录 一&#xff1a;&#x1f525; QtHelloWorld程序 &#x1f98b; 使⽤"标签"实现纯代码⽅式实现可视化操作实现 &#x1f98b; 使⽤"按钮"实现可视化操作实现纯代码实现…

游戏引擎学习第113天

仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板&#xff1a;优化的基本过程 在游戏编程中&#xff0c;优化是一个非常重要的学习内容&#xff0c;尤其是想要成为专业开发者时。优化的核心是理解代码的执行速度&#xff0c;以及如何提升其性能。在这个阶段&#xff0c;已经…

Linux 本地部署 Deepseek-R1 大模型!

DeepSeek-R1 的发布&#xff0c;掀起了一场风暴&#xff01; 开源、强大、本地可部署&#xff0c;真正私有的 AI 助手&#xff0c;不受网络、隐私等限制&#xff0c;数据安全感直接拉满&#xff01; 今天&#xff0c;手把手带你在 Linux 上本地部署 DeepSeek-R1&#xff0c;关…

【深度学习】Pytorch的深入理解和研究

一、Pytorch核心理解 PyTorch 是一个灵活且强大的深度学习框架&#xff0c;广泛应用于研究和工业领域。要深入理解和研究 PyTorch&#xff0c;需要从其核心概念、底层机制以及高级功能入手。以下是对 PyTorch 的深入理解与研究的详细说明。 1. 概念 动态计算图&#xff08;D…

IDEA + 通义灵码AI程序员:快速构建DDD后端工程模板

作者&#xff1a;陈荣健 IDEA 通义灵码AI程序员&#xff1a;快速构建DDD后端工程模板 在软件开发过程中&#xff0c;一个清晰、可维护、可扩展的架构至关重要。领域驱动设计 (DDD) 是一种软件开发方法&#xff0c;它强调将软件模型与业务领域紧密结合&#xff0c;从而构建更…

内容中台重构企业内容管理的价值维度与实施路径

内容概要 在数字化转型进程中&#xff0c;企业内容管理&#xff08;ECM&#xff09;与内容中台的差异性体现在价值维度的重构与能力边界的突破。传统ECM系统通常聚焦于文档存储、权限控制等基础功能&#xff0c;而内容中台通过标准化流程引擎与智能工具链&#xff0c;构建起覆…

CPU封装形式解析:从传统到先进封装的技术演进

中央处理器&#xff08;CPU&#xff09;的封装技术是半导体制造的关键环节&#xff0c;直接影响芯片的电气性能、散热效率和物理可靠性。随着半导体工艺的不断进步&#xff0c;封装形式从早期的简单结构演变为复杂的多维集成方案。本文将系统解析CPU的主流封装形式及其技术特点…

Spring Boot 应用(官网文档解读)

Spring Boot 启动方式 SpringApplication.run(MyApplication.class, args); Spring Boot 故障分析器 在Spring Boot 项目启动发生错误的时候&#xff0c;我们通常可以看到上面的内容&#xff0c;即 APPLICATION FAILED TO START&#xff0c;以及后面的错误描述。这个功能是通过…

【智能客服】ChatGPT大模型话术优化落地方案

本文原创作者:姚瑞南 AI-agent 大模型运营专家,先后任职于美团、猎聘等中大厂AI训练专家和智能运营专家岗;多年人工智能行业智能产品运营及大模型落地经验,拥有AI外呼方向国家专利与PMP项目管理证书。(转载需经授权) 目录 一、项目背景 1.1 行业背景 1.2 业务现…

1.22作业

1 Web-php-unserialize __construct()与$file、__destruct() __wakeup()检查 先绕过wakeup函数&#xff1a; O:4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}1.PHP序列化的时候对public protected private变量的处理方式是不同的 public无标…

学习aigc

DALLE2 论文 Hierarchical Text-Conditional Image Generation with CLIP Latents [2204.06125] Hierarchical Text-Conditional Image Generation with CLIP LatentsAbstract page for arXiv paper 2204.06125: Hierarchical Text-Conditional Image Generation with CLIP L…

overflow-x: auto 使用鼠标实现横向滚动,区分触摸板和鼠标滚动事件的方法

假设一个 div 的滚动只设置了 overflow-x: auto 我们发现使用鼠标的滚轮是无法左右滚动的&#xff0c;但是使用笔记本电脑的触摸板&#xff0c;或者在移动设备上是可以滚动的。所以我们需要兼容一下鼠标的横向滚动功能。 我们可以监控 wheel 事件&#xff0c;然后根据位置来计…

模拟实现Java中的计时器

定时器是什么 定时器也是软件开发中的⼀个重要组件. 类似于⼀个 "闹钟". 达到⼀个设定的时间之后, 就执⾏某个指定好的代码. 前端/后端中都会用到计时器. 定时器是⼀种实际开发中⾮常常⽤的组件. ⽐如⽹络通信中, 如果对⽅ 500ms 内没有返回数据, 则断开连接尝试重…

Ubuntu 的RabbitMQ安装

目录 1.安装Erlang 查看erlang版本 退出命令 2. 安装 RabbitMQ 3.确认安装结果 4.安装RabbitMQ管理界面 5.启动服务并访问 1.启动服务 2.查看服务状态 3.通过IP:port 访问界面 4.添加管理员用户 a&#xff09;添加用户名&#xff1a;admin&#xff0c;密码&#xff1…

vim修改只读文件

现象 解决方案 对于有root权限的用户&#xff0c;在命令行输入 :wq! 即可强制保存退出

【DeepSeek】本地部署,保姆级教程

deepseek网站链接传送门&#xff1a;DeepSeek 在这里主要介绍DeepSeek的两种部署方法&#xff0c;一种是调用API&#xff0c;一种是本地部署。 一、API调用 1.进入网址Cherry Studio - 全能的AI助手选择立即下载 2.安装时位置建议放在其他盘&#xff0c;不要放c盘 3.进入软件后…

NCRE证书构成:全国计算机等级考试证书体系详解

全国计算机等级考试&#xff08;NCRE&#xff09;证书体系为中学生提供了一个系统学习和提升计算机能力的平台。本文将详细介绍 NCRE 证书的构成&#xff0c;帮助中学生了解 NCRE 证书的级别和内容&#xff0c;规划未来职业发展。 一、NCRE 证书体系概述 NCRE 证书共分为四个级…

如何在WPS打开的word、excel文件中,使用AI?

1、百度搜索&#xff1a;Office AI官方下载 或者直接打开网址&#xff1a;https://www.office-ai.cn/static/introductions/officeai/smartdownload.html 打开后会直接提示开始下载中&#xff0c;下载完成后会让其选择下载存放位置&#xff1a; 选择位置&#xff0c;然后命名文…