Redis 优化秒杀(异步秒杀)

目录

为什么需要异步秒杀

异步优化的核心逻辑是什么?

阻塞队列的特点是什么?

  Lua脚本在这里的作用是什么?

异步调用创建订单的具体逻辑是什么?

为什么要用代理对象proxy调用createVoucherOrder方法?

对于代码的详细解释:

SECKILL_ORDER_EXECUTOR 是什么?

@PostConstruct 是什么?

VoucherOrderHandler 是什么?

VoucherOrderHandler 调用的handleVoucherOrder:

数据库操作的注意点有哪些?

seckillVoucher 方法: 

单线程线程池、阻塞队列、seckillVoucher 和 VoucherOrderHandler 的协作过程总结

方法调用流程总览

方法逻辑一览表

完整代码 


在秒杀场景中,我们可以将库存存入 Redis,并通过 Lua 脚本来判断用户是否有秒杀资格,同时实现一人一单的限制。由于 Redis 的单线程特性和 Lua 脚本的原子性保障,能够避免多个线程交叉执行 Redis 命令导致的并发问题。同时,使用阻塞队列将订单请求进行缓冲,当线程尝试从队列中获取订单时,如果队列为空,线程会被阻塞,直到有新订单加入队列,线程才会被唤醒并处理订单,从而实现高效的生产者-消费者模型。

为什么需要异步秒杀

1. 防止数据库压力过载

  • 异步秒杀通过将订单请求写入阻塞队列,削峰填谷,避免将瞬时高并发请求直接传递到数据库。
  • 消费者线程从队列中按顺序取出订单进行处理,减少数据库同时处理的请求量。

2. 提升系统响应速度

  • 秒杀请求在异步架构中:
    1. 同步部分:快速返回秒杀结果(例如秒杀资格校验)。
    2. 异步部分:订单的具体处理(如扣减库存、保存订单)放到后台处理。
  • 这种分离让用户能快速得到响应,而系统后台有更多时间处理复杂的订单逻辑。

异步优化的核心逻辑是什么?

问:为什么需要异步优化秒杀订单? 答:在高并发场景中,秒杀会同时产生大量订单请求。如果直接将请求交给数据库处理,容易导致数据库压力过大,从而系统崩溃。异步优化通过使用阻塞队列将订单请求排队,避免直接对数据库产生瞬时高负载。

问:如何实现异步处理? 答:将订单信息保存到阻塞队列中,使用单线程(线程池中的线程)从队列中按顺序取出订单进行处理。这样可以削峰填谷,减轻数据库压力。


阻塞队列的特点是什么?

问:阻塞队列的作用是什么? 答:阻塞队列是线程安全的队列,支持生产者-消费者模型在代码中,生产者是seckillVoucher方法,它将订单信息加入阻塞队列;消费者是VoucherOrderHandler线程,它从队列中取出订单进行处理。

问:为什么使用阻塞队列? 答:阻塞队列的特点是,如果队列为空,消费者线程会阻塞等待;如果队列满了,生产者线程会阻塞等待。这样可以很好地协调生产者和消费者的速度,避免资源浪费或超负荷。


  Lua脚本在这里的作用是什么?

问:为什么使用Lua脚本操作Redis? 答:Lua脚本在Redis中是原子执行的。使用Lua脚本可以保证秒杀资格验证和库存扣减的原子性,避免并发问题。

问:Lua脚本验证了什么? 答:

  1. 用户是否重复下单(通过Redis中存储的用户信息判断)。
  2. 秒杀库存是否充足(通过Redis中存储的库存数量判断)。
-- 参数
-- 优惠券id
local voucherId = ARGV[1]
-- 用户id
local userId = ARGV[2]-- 数据key
local stockKey = 'seckill:stock:'.. voucherId
local orderKey = 'seckill:order:'.. voucherId-- 检查库存是否足够
if (tonumber(redis.call('get', stockKey)) <= 0) thenreturn 1 -- 库存不足
end-- 检查用户是否重复下单
if (redis.call('sismember', orderKey, userId) == 1) thenreturn 2 -- 重复下单
end-- 减少库存并记录订单
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, userId)
return 0

将秒杀券的库存以String形式存入Redis

 @Override@Transactionalpublic void addSeckillVoucher(Voucher voucher) {// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher = new SeckillVoucher();seckillVoucher.setVoucherId(voucher.getId());seckillVoucher.setStock(voucher.getStock());seckillVoucher.setBeginTime(voucher.getBeginTime());seckillVoucher.setEndTime(voucher.getEndTime());seckillVoucherService.save(seckillVoucher);//  保存秒杀 库存 存入Redis当中stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(),voucher.getStock().toString());}

异步调用创建订单的具体逻辑是什么?

@Override
public Result seckillVoucher(Long voucherId) {Long userId = UserHolder.getUser().getId();// 1. 校验秒杀资格Long res = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());if (res != 0) {// 秒杀资格校验失败return Result.fail(res == 1 ? "库存不足" : "重复下单");}// 2. 生成订单信息VoucherOrder voucherOrder = new VoucherOrder();long orderID = redisIdWorker.nextId("order");voucherOrder.setId(orderID);voucherOrder.setUserId(userId);voucherOrder.setVoucherId(voucherId);// 3. 将订单信息放入阻塞队列orderTasks.add(voucherOrder);// 获取代理对象proxy = (IVoucherOrderService) AopContext.currentProxy();return Result.ok(orderID);
}

问:seckillVoucher方法中发生了什么? 答:这是异步调用的入口逻辑,分为以下几个步骤:

  1. 验证秒杀资格
    • 使用Lua脚本操作Redis,确保原子性。
    • 判断用户是否重复下单,或者库存是否不足。
    • 如果秒杀资格验证失败,则直接返回错误信息。
  2. 生成订单信息
    • 使用RedisIdWorker生成订单ID。
    • 将订单信息(用户ID、代金券ID等)封装成VoucherOrder对象。
  3. 将订单信息保存到阻塞队列
    • 调用orderTasks.add(voucherOrder)将订单加入阻塞队列中。
  4. 返回订单ID
    • 在返回给用户订单ID时,并没有真正完成订单,而是进入队列等待处理。

为什么要用代理对象proxy调用createVoucherOrder方法?

问:为什么不直接调用createVoucherOrder

答:因为 createVoucherOrder 方法是事务方法,需要通过代理对象调用才能生效。

  1. Spring 的事务机制基于 AOP(面向切面编程)实现

    • Spring 使用代理对象(动态代理或 CGLIB 代理)来拦截对事务方法的调用,并在方法执行前后添加事务管理逻辑(如开启事务、提交事务或回滚事务)。
    • 如果直接调用类内部的事务方法,调用不会经过代理对象,而是直接执行原始方法,Spring 的事务管理器无法介入,导致事务逻辑失效。
  2. 内部调用的问题

    • 在类的内部直接调用另一个事务方法时,调用不会经过代理对象,而是通过 this 调用,因此事务拦截器不会生效,事务注解(@Transactional)失效。

问:代理对象是如何获取的?

  • 将代理对象声明为一个成员变量,通过 AopContext.currentProxy() 获取当前类的代理对象。
  • 原因AopContext.currentProxy() 返回的是 Spring AOP 生成的当前类的代理对象,它能够拦截方法调用,从而触发事务管理逻辑。
  • 在异步线程中直接调用当前类的方法时,事务不会生效,因为直接调用是通过 this 引用,而不是代理对象调用。通过成员变量保存的代理对象,即使在异步线程中调用方法,也可以确保事务逻辑有效。
  • 最终,通过代理对象调用 createVoucherOrder 方法,可以正常触发 Spring 的事务管理器,确保事务功能生效。


对于代码的详细解释:

private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();private final BlockingQueue<VoucherOrder> orderTasks = new LinkedBlockingQueue<>();
@PostConstruct
private void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}private class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {// 获取队列当中的订单VoucherOrder voucherOrder = orderTasks.take();handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("Error processing order", e);}}}}

SECKILL_ORDER_EXECUTOR 是什么?

SECKILL_ORDER_EXECUTOR 是一个 单线程线程池,用来处理秒杀订单的异步任务。

private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

单线程线程池的特点是:线程池中始终只有一个线程,任务会按顺序执行,适合需要顺序处理的场景。

它的主要作用是 管理和调度线程的生命周期。具体来说:

启动和管理消费者线程

  • VoucherOrderHandler 需要一个线程不断运行,用来从阻塞队列中取订单并处理。
  • 线程池 SECKILL_ORDER_EXECUTOR 的作用是启动这个线程,并保证这个线程的生命周期由线程池管理。

线程复用

  • 如果你手动创建线程(new Thread()),可能会导致系统频繁创建和销毁线程,浪费系统资源。
  • 使用线程池可以复用线程,减少线程的创建和销毁开销,提高性能。

稳定性

  • 如果 VoucherOrderHandler 线程在执行中意外退出(例如抛出未捕获异常),线程池会自动接管并重新启动线程,保证任务不会中断。

在这里,SECKILL_ORDER_EXECUTOR 通过单线程的方式从阻塞队列中取出订单,按顺序处理,确保秒杀订单的处理逻辑是线程安全的。


@PostConstruct 是什么?

@PostConstruct 是 Java 的一个注解,作用是在 Spring 容器将 Bean 初始化完成后,立即执行标注的方法。换句话说,当 Spring 加载并创建了 VoucherOrderServiceImpl 实例后,会自动调用 init() 方法。

这是一个生命周期回调方法,常用于初始化逻辑,比如启动线程、加载配置等。


 init() 方法的作用是什么?

  • 这个方法的主要作用是 启动一个专用线程(由单线程线程池管理),用于从阻塞队列中取出订单并进行异步处理。
  • 通过 SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());,将 VoucherOrderHandler 提交到线程池中,线程池会启动一个线程,持续运行 VoucherOrderHandler 中的逻辑。

VoucherOrderHandler 是什么?

VoucherOrderHandler 是一个内部类,它实现了 Runnable 接口,代表一个任务。

  • 任务的核心逻辑是:从阻塞队列中取出订单并处理
  • 它的 run() 方法包含一个 while(true) 循环,这样线程会一直运行,不断从队列中取出订单(通过 orderTasks.take()),直到程序终止。

VoucherOrderHandler 调用的handleVoucherOrder:

  • 防止同一用户多次下单(重复下单)。
  • 调用执行订单的具体业务逻辑的方法createVoucherOrder
    (如扣减库存、保存订单等)。
        @Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {// 实现一人一单,我们需要先判断该用户是否已经抢过了// 根据优惠券id和用户id查询订单Long userId = UserHolder.getUser().getId();int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder).count();if (count > 0) {log.error("已经购买过,不可重复购买!");}// 扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherOrder).
    //                eq("stock",voucher.getStock()). // 加个乐观锁,如果现在的库存和我之前查询的库存不相同,说明在我之前就有线程修改了数据库gt("stock", 0).update();if (!success) {log.error("库存不足!");}// 写入数据库 不需要再返回orderId了,因为之前在seckillVoucher已经返回了save(voucherOrder);}

数据库操作的注意点有哪些?

问:如何实现一人一单的限制? 答:在createVoucherOrder方法中,通过查询数据库判断用户是否已经购买过对应的代金券。

问:如何扣减库存? 答:使用seckillVoucherService执行SQL语句更新库存,并通过gt(\"stock\", 0)确保库存大于0。


整体逻辑总结

  • 触发时机: 当 VoucherOrderServiceImpl 被 Spring 加载并实例化后,@PostConstruct 注解标注的 init() 方法会被调用。

  • 作用init() 方法向线程池提交了一个 VoucherOrderHandler 任务,这个任务会启动一个线程,不断从阻塞队列中取出订单并调用相关处理逻辑(handleVoucherOrder)。


seckillVoucher 方法: 

单线程线程池、阻塞队列、seckillVoucher VoucherOrderHandler 的协作过程总结

seckillVoucher 是厨师

  • 它负责接收顾客的订单请求(秒杀请求),检查是否符合要求(库存是否足够、是否重复下单),然后生成订单(菜品)并放在桌子上(阻塞队列)。
  • 核心职责:生产订单,确保每个订单合法并生成完整订单信息。

BlockingQueue 是桌子

  • 它负责临时存放厨师制作好的订单(菜品),保证每个订单都按顺序排列。
  • 如果桌子空了,顾客(消费者线程)只能等;如果桌子满了,厨师(生产者线程)也需要暂停制作。
  • 核心职责:缓冲区,用于在生产和消费之间解耦。

VoucherOrderHandler 是顾客

  • 它负责从桌子上取菜(从队列中取订单),并最终消费(处理订单,包括扣减库存、写入数据库等)。
  • 如果桌子没有菜了,它会耐心等待;一旦有菜,它会立刻取走并处理。
  • 核心职责:消费订单,执行订单处理逻辑。

SECKILL_ORDER_EXECUTOR 是服务员

  • 它负责启动和管理顾客(消费者线程),确保顾客始终在桌子旁边等待取菜。
  • 如果顾客突然有事不要菜品了(比如异常退出),服务员会招待一个新的顾客来接替。
  • 核心职责:管理消费者线程的生命周期,确保订单处理不断运行。


方法调用流程总览

  1. 用户发起秒杀请求,触发 seckillVoucher 方法。
  2. seckillVoucher 验证秒杀资格并将订单放入阻塞队列。
  3. VoucherOrderHandler(由线程池管理的消费者线程)从队列中取出订单,调用 handleVoucherOrder 进行处理。
  4. handleVoucherOrder 利用分布式锁防止重复下单,并调用 createVoucherOrder 完成订单的核心逻辑。
  5. createVoucherOrder 执行订单的最终处理,包括扣减库存、写入数据库等。

方法逻辑一览表

方法作用关键逻辑
seckillVoucher秒杀请求入口,生成订单并加入阻塞队列验证秒杀资格,生成订单信息,加入阻塞队列。
阻塞队列 (BlockingQueue)存储订单信息,实现生产者与消费者的解耦线程安全存储,缓冲生产者和消费者速度差异。
VoucherOrderHandler消费者线程,从队列中取订单并调用处理方法从队列取订单,调用 handleVoucherOrder
handleVoucherOrder防止重复下单,调用核心业务逻辑创建分布式锁,防止重复下单,调用 createVoucherOrder
createVoucherOrder执行订单的核心逻辑校验订单、扣减库存、保存订单到数据库。

完整代码 

@Service
@RequiredArgsConstructor
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;final RedisIdWorker redisIdWorker;final StringRedisTemplate stringRedisTemplate;final RedissonClient redissonClient;private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();private IVoucherOrderService proxy;private final BlockingQueue<VoucherOrder> orderTasks = new LinkedBlockingQueue<>();@PostConstructprivate void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private class VoucherOrderHandler implements Runnable {@Overridepublic void run() {while (true) {try {// 获取队列当中的订单VoucherOrder voucherOrder = orderTasks.take();handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error("Error processing order", e);}}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {Long userId = voucherOrder.getUserId();// 1. Create lockRLock lock = redissonClient.getLock("lock:order:" + userId);// 2. Try to acquire lockboolean isLock = lock.tryLock();if (!isLock) {log.error("Duplicate order not allowed");return;}try {// 3. Create order via proxyproxy.createVoucherOrder(voucherOrder);} finally {// 4. Release locklock.unlock();}}/*** 基于异步Lua脚本保证原子性** @param voucherId* @return*/@Overridepublic Result seckillVoucher(Long voucherId) {Long userId = UserHolder.getUser().getId();// 执行Lua脚本Long res = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(),userId.toString());// 判断返回值是否为0if (res != 0) {// 非0 则没有秒杀资格return Result.fail(res == 1 ? "库存不足" : "重复下单");}// 从Redis当中获取下单信息long orderId = redisIdWorker.nextId("order");// TODO 为0 表示有秒杀资格 需要将下单信息保存在阻塞队列当中// 创建订单VoucherOrder voucherOrder = new VoucherOrder();// 订单idlong orderID = redisIdWorker.nextId("order");voucherOrder.setId(orderID);// 用户idvoucherOrder.setUserId(UserHolder.getUser().getId());// 代金券idvoucherOrder.setVoucherId(voucherId);// 保存到阻塞队列当中orderTasks.add(voucherOrder);// 获取代理对象proxy = (IVoucherOrderService) AopContext.currentProxy();return Result.ok(orderId);}@Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {// 实现一人一单,我们需要先判断该用户是否已经抢过了// 根据优惠券id和用户id查询订单Long userId = UserHolder.getUser().getId();int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder).count();if (count > 0) {log.error("已经购买过,不可重复购买!");}// 扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherOrder).
//                eq("stock",voucher.getStock()). // 加个乐观锁,如果现在的库存和我之前查询的库存不相同,说明在我之前就有线程修改了数据库gt("stock", 0).update();if (!success) {log.error("库存不足!");}// 写入数据库save(voucherOrder);}
}

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

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

相关文章

C++笔记之`size_t`辨析

C++笔记之size_t辨析 code review! 文章目录 C++笔记之`size_t`辨析一.什么是 `size_t`?二.`size_t` 的来源和设计目的三.`size_t` 的应用场景四.`size_t` 的优点五.`size_t` 的缺点和注意事项六.`size_t` 和其他类型的比较七.总结与建议在 C/C++ 中,size_t 是一个非常重要的…

MySQL表的增删查改(下)——Update(更新),Delete(删除)

文章目录 Update将孙悟空同学的数学成绩修改为80分将曹孟德同学的数学成绩变更为 60 分&#xff0c;语文成绩变更为 70 分将总成绩倒数前三的 3 位同学的数学成绩加上 30 分将所有同学的语文成绩更新为原来的 2 倍 Delete删除数据删除孙悟空同学的考试成绩删除整张表数据 截断表…

大语言模型训练的数据集从哪里来?

继续上篇文章的内容说说大语言模型预训练的数据集从哪里来以及为什么互联网上的数据已经被耗尽这个说法并不专业&#xff0c;再谈谈大语言模型预训练数据集的优化思路。 1. GPT2使用的数据集是WebText&#xff0c;该数据集大概40GB&#xff0c;由OpenAI创建&#xff0c;主要内…

【hadoop学习遇见的小问题】clone克隆完之后网络连接不上问题解决

vi /etc/udev/rules.d/70-persistent-net.rules注释掉第一行 第二行的eth1 改为eth0 由上图也可以看到物理地址 记录下来在网卡中修改物理地址 vi /etc/sysconfig/network-scripts/ifcfg-eth0修改完之后 重启reboot 即可

PDFMathTranslate: Star13.8k,一款基于AI的PDF文档全文双语翻译PDF文档全文双语翻译,保留格式神器,你应该需要它

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 PDFMathTranslate是一个开源项目&#xff0c;旨在为用户提供便捷的PDF科学论文翻译解决方案。它不仅能够翻译文本&#xff0c;还能保留公式、图表、目…

对话|全年HUD前装将超330万台,疆程技术瞄准人机交互“第一屏”

2024年&#xff0c;在高阶智驾进入快速上车的同时&#xff0c;座舱人机交互也在迎来新的增长点。Chat GPT、AR-HUD、车载投影等新配置都在带来新增量机会。 高工智能汽车研究院监测数据显示&#xff0c;2024年1-10月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用…

【机器学习案列】学生抑郁可视化及预测分析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

微服务保护——Sentinel

什么是微服务保护&#xff1f; 微服务保护是一系列用于保障微服务架构稳定、可靠运行的策略与技术手段&#xff0c;在复杂的分布式微服务系统里&#xff0c;它能避免局部故障引发连锁反应&#xff0c;从而维持整个系统的可用性&#xff0c;主要涵盖以下几个关键部分&#xff1a…

福建双色荷花提取颜色

提取指定颜色 HSV双色荷花代码验证 参照《OpenCV图像处理技术》 HSV 要用HSV的色调、饱和度和亮度来提取指定颜色。 双色荷花 农林大学金山校区观音湖 代码 import cv2 import numpy as npimgcv2.imread("./sucai6/hua.jpg") cv2.imshow("SRC",img) h…

tdengine数据库使用java连接

1 首先给你的项目添加依赖 <dependency> <groupId>com.taosdata.jdbc</groupId> <artifactId>taos-jdbcdriver</artifactId> <version>3.4.0</version> <!-- 表示依赖不会传递 --> </dependency> 注意&am…

MIUI显示/隐藏5G开关的方法,信号弱时开启手机Wifi通话方法

5G网速虽快&#xff0c;手机功耗也大。 1.取消MIUI强制的5G&#xff0c;手动设置4G的方法&#xff01; 【小米澎湃OS, Xiaomi HyperOS显示/隐藏5G开关的方法】 1.1.小米MIUI系统升级后&#xff0c;被强制连5G&#xff0c;手动设置开关被隐藏&#xff0c;如下图&#xff1a; 1…

pikachu - Cross-Site Scripting(XSS)

pikachu - Cross-Site Scripting&#xff08;XSS&#xff09; 声明&#xff01; 笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人无关&#xff0c;切勿触碰法律底线&#xff0c;否则后果自负&#x…

部署:上传项目代码 配置数据库

一、上传代码 1、使用git 可以使用Git Clone。使用前&#xff0c;在服务器上也要创建秘钥对。这里的密钥对&#xff0c;是专门用来读取Git仓库的。 在宝塔上&#xff0c;点击终端。进来后&#xff0c;运行 ssh-keygen还是一路回车&#xff0c;密钥对就建好了。 接着用命令…

时敏软件定义网络的服务保证

论文标题&#xff1a; Service Guarantees for Time-Sensitive Software-Defined Networks作者信息&#xff1a; Weijiang Kong论文出处&#xff1a; Eindhoven University of Technology, 2025年1月20日 摘要&#xff1a; 在过去十年中&#xff0c;随着半导体技术的进步和对更…

【Linux】sed编辑器

一、基本介绍 sed编辑器也叫流编辑器&#xff08;stream editor&#xff09;&#xff0c;它是根据事先设计好得一组规则编辑数据流。 交互式文本编辑器&#xff08;如Vim&#xff09;中&#xff0c;可以用键盘命令交互式地插入、删除或替换文本数据。 sed编辑器是根据命令处理…

嵌入式入门Day40

C Day3 C对C的结构体的扩充类this指针类的大小类中的特殊成员函数构造函数 作业 C对C的结构体的扩充 C语言中的结构体&#xff0c;仅仅只是属性&#xff08;变量&#xff09;的聚合体&#xff0c;不可以在结构体中定义行为&#xff08;函数&#xff09;。如果非要在结构体中定…

《自动驾驶与机器人中的SLAM技术》ch2:基础数学知识

目录 2.1 几何学 向量的内积和外积 旋转矩阵 旋转向量 四元数 李群和李代数 SO(3)上的 BCH 线性近似式 2.2 运动学 李群视角下的运动学 SO(3) t 上的运动学 线速度和加速度 扰动模型和雅可比矩阵 典型算例&#xff1a;对向量进行旋转 典型算例&#xff1a;旋转的复合 2.3 …

C语言教程——指针进阶(1)

目录 前言 1、字符指针 2、指针数组 3、数组指针 3.1数组指针 3.2&数组名VS数组名 3.3数组指针的使用 4、数组参数、指针参数 4.1一维数组传参 4.2二维数组传参 4.3一级指针传参 4.4二级指针传参 4.5总结 5、函数指针 5.1思考 总结 前言 我们在之前知道指针…

[应用类App] 轮廓线 aia源码 UI界面精美,画布实现手柄摇杆

屏幕数量&#xff1a;10个&#xff0c;仅主界面近3000代码块&#xff0c;请自行研究参考。 实现了手柄摇杆功能&#xff0c;界面做的比较好。 下载地址&#xff1a;轮廓线 aia源码 UI界面精美&#xff0c;画布实现手柄摇杆 - .aia 案例源码 - 清泛IT社区&#xff0c;为创新赋能…

C++—9、如何在Microsoft Visual Studio中调试C++

本文通过实例操作来介绍 Visual Studio 调试器的功能。调试器在运行过程中可提供许多方法让你查看代码的情况。 你可以逐步浏览代码、查看变量中存储的值、设置对变量的监视以查看值何时改变、检查代码的执行路径、查看代码分支是否正在运行等等。本实例主要是设置断点及查看内…