discusses the new support for custom slash commands in Gemini CLI, a feature that allows users to define reusable prompts for streamlining interactions and improving efficiency. These slash commands can be defined in local .toml files or through Model Context Protocol (MCP) prompts
AI|如何用记笔记的模式写提示词(prompt)让LLM能完成复杂任务
本文字数: 2k 阅读时长 ≈ 2 分钟
以下介绍一种减少LLM在执行复杂任务过程中产生幻觉的技巧,让ai更好完成任务,示例需结合gemini-cli使用,在其他工具中使用方法类似
提示词
指定ai能使用的工具,定义好输入/输出格式,拆分步骤,设定好笔记格式,让ai分步执行,并在每步完成后记到笔记文件中直到完成任务.
如下是一个翻译网页提示词:
1 | 输入: |
封装为gemini-cli命令
将提示词写到文件中 ~/.gemini/commands/translate.toml
1 | prompt = """ |
即可这样使用
1 | gemini |
参考
- video
- prompt
redis|为了优化AOF文件大小使用异步线程BGREWRITEAOF重写日志文件,redis是如何解决数据不一致的?
在 Redis 中,BGREWRITEAOF 是一个用于重写 AOF(Append Only File)文件的后台操作,目的是压缩 AOF 文件,去除冗余命令,从而减小文件体积并提升恢复速度。然而,在执行 BGREWRITEAOF 期间,主进程仍在处理客户端请求,可能会产生新的写入操作。这就带来了一个潜在问题:主进程与子进程(BGREWRITEAOF 子进程)之间可能出现数据不一致。
问题本质:主进程与子进程的数据不一致
- 主进程:正常处理写操作,将命令追加到 AOF 缓冲区,并可能写入 AOF 文件。
- 子进程(BGREWRITEAOF):在某一时刻(通常是
BGREWRITEAOF命令执行时)启动,它会读取当前 Redis 的完整数据集(内存中的键值对),并将其以命令形式写入一个新的 AOF 文件。 - 问题:在子进程执行期间,主进程可能已经接收并处理了新的写操作,这些写操作没有被子进程看到,导致新 AOF 文件中缺少这部分数据。
Redis 如何解决这个问题?
Redis 采用了一种称为 “AOF 重写缓冲区”(AOF rewrite buffer) 的机制来解决这一问题。
✅ 核心机制:AOF 重写缓冲区(AOF Rewrite Buffer)
子进程启动时:
- Redis 主进程会创建一个新的 AOF 文件(临时文件,如
temp-rewriteaof-bg-*.aof)。 - 子进程开始读取内存中的数据集,并将所有键值对以命令形式写入这个新文件。
- Redis 主进程会创建一个新的 AOF 文件(临时文件,如
主进程在子进程运行期间:
- 所有新的写操作(如
SET key value)不仅写入 AOF 缓冲区,还会额外写入一个特殊的缓冲区 —— AOF 重写缓冲区。 - 这个缓冲区是独立的,用于记录子进程运行期间的增量操作。
- 所有新的写操作(如
子进程完成重写后:
- 子进程退出。
- 主进程将 AOF 重写缓冲区中的所有命令追加写入到新 AOF 文件末尾。
- 然后将新 AOF 文件原子替换为旧的 AOF 文件(通过
rename系统调用)。
最终结果:
- 新的 AOF 文件包含了:
- 子进程重写时的完整数据集(即快照)。
- 子进程运行期间所有新增的写操作。
- 因此,新 AOF 文件是完整且一致的,能完整恢复 Redis 的状态。
- 新的 AOF 文件包含了:
关键点
| 机制 | 作用 |
|---|---|
| AOF 重写缓冲区 | 缓存子进程运行期间的增量写操作 |
| 子进程读内存快照 | 生成 AOF 的基础数据(无冗余命令) |
| 主进程追加缓冲区内容 | 确保不丢失子进程运行期间的写操作 |
| 原子替换文件 | 保证 AOF 文件切换的原子性和一致性 |
注意事项
AOF 重写缓冲区大小有限:
- 如果缓冲区溢出(例如写入太频繁),Redis 会阻塞主进程,直到子进程完成或缓冲区清空。
- 可通过配置
aof-rewrite-incremental-fsync和aof-rewrite-buffer-size调优。
性能影响:
BGREWRITEAOF是 CPU 密集型操作,可能影响主进程性能。- 建议在低峰期执行,或使用
auto-aof-rewrite自动触发。
AOF 重写期间主进程仍可服务:
- 重写过程是异步的,主进程不阻塞(除了缓冲区满时可能阻塞)。
示例流程
1 | # 主进程执行 BGREWRITEAOF |
✅ 总结
Redis 通过 AOF 重写缓冲区 机制,完美解决了 BGREWRITEAOF 与主进程数据不一致的问题:
子进程负责“快照”,主进程负责“增量”,最后合并,确保 AOF 文件完整、一致、可恢复。
go|在局部函数中返回sync.RWMutex指针合适吗
sync.RWMutex 的基本特性
- 值类型:
sync.RWMutex是一个 struct。这意味着如果你直接传递它,你将得到一个副本。对副本的锁定操作不会影响原始的 mutex,这会导致同步失效。 - 不应在第一次使用后复制: Go 官方文档明确指出,
sync.Mutex(以及sync.RWMutex) 不应在第一次使用(即调用Lock/Unlock或RLock/RUnlock)后进行复制。复制会导致未定义的行为,通常是死锁或 panic。 - 零值可用:
sync.RWMutex{}(零值) 是一个有效的、可直接使用的 mutex。
在局部函数中创建并返回 *sync.RWMutex 的情况
考虑以下代码:
1 | package main |
分析:
- 技术上可行: Go 的逃逸分析会识别到局部变量
mu的地址被返回了函数外部,因此它会被分配到堆上(heap),而不是栈上。所以,返回的指针是有效的,不会指向已被销毁的内存。 - 不常见,且通常不是好的设计模式:
- 封装性差:
sync.RWMutex的核心作用是保护共享数据。它几乎总是作为某个数据结构(struct)的字段而存在,由该数据结构的方法来管理其锁定和解锁,以保护该结构体内部的数据。 - 职责不清: 如果一个函数仅仅返回一个
*sync.RWMutex,那么这个 mutex 是为了保护什么数据?这个信息并没有被封装起来,使用者需要自己去追踪这个 mutex 的用途,这增加了代码的复杂性和出错的风险。 - 容易误用: 调用者可能会不清楚这个 mutex 应该保护什么数据,或者在不适当的时候加锁/解锁,导致死锁、竞争条件等问题。
- 代码可读性差: 相比于一个明确的结构体(例如
SafeCache)包含一个RWMutex,仅仅返回一个RWMutex指针会让代码意图模糊。
- 封装性差:
在局部函数中返回已存在的 *sync.RWMutex 指针
另一种情况是,函数不是创建新的 RWMutex,而是返回一个指向已经存在的 RWMutex 的指针,比如从一个全局变量或者传入参数的结构体中:
1 | package main |
分析:
- 技术上可行: 同样,返回的指针是有效的。
- 合适性存疑:
- 暴露内部实现: 这种做法打破了封装性。一个结构体通常应该通过其方法来提供对数据(包括同步机制)的受控访问,而不是直接暴露其内部的
sync.RWMutex字段。 - 耦合性增加: 外部代码直接操作内部 mutex 会增加模块间的耦合,使得结构体的内部实现更难修改。
- 潜在的API风险: 如果
MyContainer的设计者希望控制对Data的访问方式,而调用者直接使用c.Mu进行锁操作,可能会绕过MyContainer提供的方法,导致不一致或错误的状态。
- 暴露内部实现: 这种做法打破了封装性。一个结构体通常应该通过其方法来提供对数据(包括同步机制)的受控访问,而不是直接暴露其内部的
推荐做法
在 Go 中,管理 sync.RWMutex 的惯用和最佳实践是将其嵌入到一个结构体中,并通过该结构体的方法来管理锁定和解锁,从而保护结构体自身的字段。
1 | package main |
总结
在Go语言中,一般不推荐在局部函数中创建 sync.RWMutex 并单独返回其指针。这通常表示设计上存在封装性不足的问题。
更推荐的做法是:
- 将
sync.RWMutex作为你希望保护的结构体的字段。 - 通过该结构体的方法来封装对数据的访问和
sync.RWMutex的锁定/解锁操作。 - 如果需要提供一个同步的实例,你的构造函数应该返回一个指向该结构体实例的指针(例如
*SafeCounter),而不是单独的*sync.RWMutex指针。
这样做可以提高代码的封装性、可读性和健壮性,减少误用的可能性。
Go,何时使用泛型
Go 语言自 1.18 版本开始支持泛型(Generics)。泛型允许我们为函数和数据结构定义类型参数,增强代码的复用性和类型安全性。然而,泛型并不是在所有情况下都适用,正确地使用泛型能带来代码清晰性和性能提升,错误使用则可能使代码复杂或降低性能。
本文旨在帮助你了解在什么情况下应该考虑使用泛型。
泛型的优点
- 代码复用性:泛型允许用一种实现支持多种类型,减少代码重复。
- 类型安全:在编译时检查类型,防止运行时类型错误。
- 抽象能力:抽象地处理数据结构和算法,提升代码表达力。
- 性能:避免因为使用
interface{}类型带来的类型断言,从而减少运行时开销。
何时使用泛型?
泛型适用于以下场景:
- 你有一组算法或数据结构逻辑,它们在不同的类型上运作,但逻辑完全相同。
- 你想写高度通用的代码来简化代码库,同时保持类型安全。
- 你的代码需要同时处理多种类型,但不想牺牲性能。
- 你希望避免冗余代码,减少维护工作量。
何时不适合使用泛型?
泛型不是万金油,某些情况下反而带来负担:
- 只有单一具体类型需求时,泛型会增加代码复杂度。
- 泛型代码过于复杂,导致可读性变差,增加维护难度。
- 性能占优且简单逻辑可以接受轻微重复代码时,不必强制用泛型。
- 当泛型参数变得过于复杂(过多约束、嵌套)时,可能适得其反。
不要把interface类型替换为类型参数
Go语言有interface类型,interface支持某种意义上的泛型编程。
举个例子,被广泛使用的io.Reader接口提供了一种泛型机制用于读取数据,比如支持从文件和随机数生成器里读取数据。
如果你对某些类型的变量的操作只是调用该类型的方法,那就直接使用interface类型,不要使用类型参数。io.Reader从代码角度易于阅读且高效,没必要使用类型参数。
举个例子,有人可能会把下面第1个基于interface类型的ReadSome版本修改为第2个基于类型参数的版本。
1 | func ReadSome(r io.Reader) ([]byte, error) |
不要做这种修改,使用第1个基于interface的版本会让函数更容易编写和阅读,并且函数执行效率也几乎一样。
注意:尽管可以使用不同的方式来实现泛型,并且泛型的实现可能会随着时间的推移而发生变化,但是Go 1.18中泛型的实现在很多情况下对于类型为interface的变量和类型为类型参数的变量处理非常相似。这意味着使用类型参数通常并不会比使用interface快,所以不要单纯为了程序运行速度而把interface类型修改为类型参数,因为它可能并不会运行更快。
用例示例
例如处理一组不同类型的切片排序、查找操作,可以使用泛型函数避免重复编写针对不同类型的代码。
非泛型版本
1 | func IntContains(slice []int, val int) bool { ... } |
上述重复很多类似逻辑。
泛型版本
1 | func Contains[T comparable](slice []T, val T) bool { ... } |
案例2: Go内置的容器类型
入参是一个map,要返回该map的所有key组成的slice,key的类型可以是map支持的任意key类型。
1 | // MapKeys returns a slice of all the keys in m. |
这段代码没有对map里key的类型做任何限定,并且没有用map里的value,因此这段代码适用于所有的map类型。这就是使用类型参数的一个很好的示例。
这种场景下,也可以使用反射(reflection),但是反射是一种比较别扭的编程模型,在编译期没法做静态类型检查,并且会导致运行期的速度变慢。
结语
泛型是强大的工具,但需用时权衡复杂度与收益。建议代码在泛型和具体类型之间平衡。合适时用泛型,实现代码复用和类型安全;不合适则回归简单可理解方案。
如果你正在考虑是否使用泛型,问自己:
- 这段代码是否适用于多种类型?
- 是否存在类型重复代码?
参考
Redis 知识全景图
本文字数: 137 阅读时长 ≈ 1 分钟
活动|EdgeOne国际版,测速并分享获取2个免费套餐
本文字数: 100 阅读时长 ≈ 1 分钟
腾讯旗下CDN国际版(EdgeOne)限时活动,测速并分享至Twitter(x.com)和Facebook可获取2个免费套餐
活动链接
https://edgeone.ai/get-free-plan
截图


纯视觉的幕后英雄:特斯拉如何利用激光雷达车,加速 Robotaxi 的到来
文章指出特斯拉利用激光雷达车队在奥斯汀等地收集数据,并非放弃纯视觉路线,而是为Robotaxi建立高精度的地面实况(Ground Truth)。通过时间戳同步技术,将激光雷达的精确测量值与摄像头图像自动配对,高效生成海量训练数据,从而“教导”纯视觉系统独立完成距离和速度估算,加速自动驾驶的实现。
只给好评:研究人员在论文中隐藏AI提示
《日经亚洲》报道,来自八个国家14个学术机构的研究人员被发现在论文中隐藏提示,以操纵AI审稿人给出正面评价,引发了关于学术诚信和AI在同行评审中作用的激烈辩论。
Go|如何判断两个IP是否在同一个网段
本文字数: 2.6k 阅读时长 ≈ 2 分钟
介绍如何判断两个IP地址是否属于同一个网段,解释其底层原理,并提供Go语言的实现代码。

