OpenClaw多Agent实战:从0到1搭多角色协作机器人,踩遍所有坑!

各位极客老铁,老墨又来填坑了! 最近深耕OpenClaw多Agent模式,目标很简单:让main机器人当调度员,mr(市场研究员)、pm(产品经理)、dev(开发工程师)各干各的活,形成一个小型AI协作团队。但过程真的一言难尽,从命令行操作到飞书配置,踩了N个暗坑,今天把完整搭建流程+避坑指南甩给你们,全程实操,跟着走不迷路——毕竟老墨从不玩虚的,只讲能落地的干货! 为什么需要多个Agent呢? 先交代背景:很多老铁会问,搞一个main机器人不就够了?为啥非要折腾多个Agent? 老墨实测总结:单Agent就是“万金油”,啥都能做但啥都不精——比如让一个机器人既做市场调研、又做产品规划、还写代码,做的事情太多了,记忆存储也会庞大,很容易出现“AI幻觉”。 我要搭建的多Agent架构,核心是「1个调度Agent(main/小墨)+ 3个功能Agent(mr/pm/dev)」,所有Agent接入飞书群组,实现“@对应角色触发对应响应”,目前已完成群组呼叫、私聊,协同工作还需进一步研究,看后文详述。 多Agent的核心价值,就是「分工明确、各司其职」:main机器人当调度员,负责统筹需求、分配任务;mr专注市场调研,pm聚焦产品规划,dev专攻技术实现,既避免了单Agent的混乱,又能提升响应效率和专业性,后续还能实现协同工作,搭建一个小型AI协作团队。 顺便说一句,为了使用飞书插件最新的能力,我把openclaw原生的飞书插件替换成了,我测试了一下,他们的配置是兼容的,你可以不用按照我的来,如果配置不兼容你可以参考飞书官方插件使用文档和OpenClaw飞书官方文档。 前言 由于OpenClaw版本更新非常快,文档更新不及时,网上查询资料时老墨也深受其害,配置各不相同,非常麻烦。所以老墨这里把环境标清楚,避免你跳坑: OpenClaw版本: 我的是 2026.3.13,非常影响后文讲述的配置,不同版本差异非常大,如果你的版本与我不一致,那么你需要注意 系统环境: Windows 10,这个影响不是很大,主要注意windows和linux路径上的区别即可 另外,你可以了解一下飞书的id设计,每一个机器人看到的你的 openid并不是一样的,如果你用到了需要特别注意: openid:以 ou 开头,格式为 ou_xxx 群聊的id: 以 oc 开头,格式为 oc_xxx Appid: 飞书机器人的appid格式时 cli_xxx 一、第一步:添加多个工作区(多Agent的基础,别瞎建目录) OpenClaw的多Agent,本质是「每个Agent对应一个独立工作区」,工作区里存着该Agent的身份配置(soul.md/identity.md等)、日志和任务数据,这一步是基础,建错路径后续全白搭。 很多新手上来就手动建文件夹,结果OpenClaw识别不到,老墨实测:用命令行创建最稳妥,自动关联配置,避免路径错乱。 先查看一下你的agent列表: 1openclaw agents list 你应该看到你本地有一个主agent,现在可以添加几个了: 1openclaw agents add mr --workspace ~/.openclaw/workspace-mr 2openclaw agents add pm --workspace ~/.openclaw/workspace-pm 3openclaw agents add dev --workspace ~/.openclaw/workspace-dev 这里展示一下我后台添加的一个writer的截图: 这里我指定工作目录在我的用户目录下的 .openclaw中,与主Agent一致。 如果你加错了,你可以删除: 1openclaw agents remove dev 创建完成后,你的~/.openclaw/openclaw.json配置文件的"agents"节点下会有"list"子节点,里边对应了你添加的多个agent,你可以修改这个配置,比如指定不同的模型: ...

2026-03-20 · 3 min · 487 words · 老墨

[GoLang避坑实战-19] 搞定基础就想收工?别天真了!揭秘 Go 进阶路上的“九九八十一坑”

大家好,我是极客老墨! 恭喜你,跟着老墨走完了 Go 语言的基础教程。从变量声明到实战开发CLI程序,从简单的 if-else 到初探并发,你已经拿到了进入 Go 世界的入场券。 但前几天有个读者私信我,一句话把我问住了: “老墨,基础教程我看完了,感觉自己无敌了,接下来是不是可以直接去面大厂拿 50K 了?” 我当时正在喝咖啡,差点没一口喷出来。 兄弟,听老墨一句劝:搞定语法只是让你学会了“拿刀”,但要想在工程实战里“切口如丝”,你离真正的 Go 高手还差着好几个“坑”呢。 今天这篇,咱们不讲新语法,咱们聊聊实话:基础完了,然后呢? 避坑回马枪:那些“以为懂了”的基础坑 在聊进阶之前,老墨不放心,得带你杀个回马枪。下面这几个基础篇里的“躲猫猫”,你真的彻底搞定了吗? 影子变量 (Shadowing):你是不是还在 if err := ... 里不小心遮蔽了全局变量,导致逻辑死活跑不通? 切片共享底层数组:你是不是还在愉快地 s2 := s1[:2],结果改了 s2 发现 s1 也崩了? 循环变量地址问题:虽然 Go 1.22 帮你填了坑,但你如果还没养成“局部副本”的意识,在老版本项目里照样会翻车。 defer 的陷阱:你是不是还在循环里写 defer?或者在 defer 里漏写了 nil 检查? 老墨建议:如果上述几个场景你还得查笔记,那说明你的基础还没到“化境”。去 go-tutorial-code 把对应的演示代码再跑一遍,那是老墨用 Bug 堆出来的血泪史。 能写和能打,是两回事 很多同学觉得自己“会写”了,但一上手真实项目就抓瞎。 第一次写 Web:信心满满开了 10 万个 Goroutine,结果服务器内存瞬间爆炸。这时你才知道,原来 Golang 的并发不是让你无脑 go func()。 第一次面大厂:面试官问你:“GMP 调度器是怎么处理阻塞的?”你一脸懵逼,心想我只要会写 API 不就行了? 第一次做架构:代码一多就乱成一团,依赖注入 (DI) 的意义在哪?接口到底该怎么抽象? 这就是**“语法选手”和“工程选手”**的区别。基础教程教你“刀怎么拿”,而进阶之路教你“仗怎么打”。 老墨的进阶地图:内功与外家功夫 想成为真正的 Go 高手,老墨认为必须“双修”: ...

2026-03-15 · 1 min · 172 words · 老墨

养虾必备的 10 个 Skills,最后一个是真神

大家好,我是极客老墨。 今天我们来盘点 OpenClaw 社区公认、高频实用的十大核心 Skills,按「安全打底→基础核心→场景提效→进阶成长」排序,覆盖你做内容创作、开发运维、日常自动化的全场景需求,老墨亲测,文末附一键安装指令。 什么是 Skill? 如果没有Skill,你会发现你的 OpenClaw 功能会非常有限。装上 Skill,相当于给它插上了翅膀,让它变得强大且聪明。 你可以把 OpenClaw 想象成一部手机,它的 Skill 就是手机里的APP:它本质上是一个可以独立运行的插件和应用程序,大模型根据需要按需调用它实现某些特定的功能。 比如 file-organizer(文件管理 Skill)是 “文件管理器 APP”,agent-browser 是 “浏览器自动化 APP”;这些 Skill 是独立的可执行程序,有自己的代码、依赖、配置,安装后能直接运行;你对 OpenClaw 说 “整理我的桌面文件”,OpenClaw 会调用 file-organizer 这个 Skill 直接执行,无需大模型额外处理。 去哪里找 Skill? 官方有个 ClawHub,这是 OpenClaw 的 Skill 社区。 我给老铁安装OpenClaw的时候一般都会把官方的 clawhub Skill装上,这样就可以通过对话的方式让 OpenClaw 自己安装 Skills 了。 但 ClawHub 经常限速,我现在更推荐用腾讯的 skillhub,国内访问更快。 安装方式: 方式1:对话安装(推荐) 直接和 OpenClaw 说: 根据 https://skillhub-1388575217.cos.ap-guangzhou.myqcloud.com/install/skillhub.md 安装Skillhub商店。 方式2:命令行安装(macOS/Linux) 1curl -fsSL https://skillhub-1251783334.cos.ap-guangzhou.myqcloud.com/install/install.sh | bash ...

2026-03-13 · 2 min · 356 words · 老墨

别再用 ls 和 cat 了!这两个工具让我的终端效率直接翻倍

作为天天和终端打交道的开发者,不管是写Go代码、跑OpenClaw自动化脚本,还是日常运维服务器,我每天敲得最多的命令,就是ls和cat。 但系统自带的这两个命令,实在太鸡肋了: ls输出黑白一片,分不清文件、文件夹、可执行文件,找东西全靠瞎看 cat看代码没有高亮,长文件翻起来巨麻烦,还要配合less、grep来回折腾 没有git状态显示,改了文件都不知道有没有提交 直到我用上了exa和bat这两个神器,直接把我的终端体验拉满,每天至少帮我省1小时的无效操作。今天就给大家出保姆级教程,5分钟就能装好用上,全平台兼容,零代码基础也能跟着做。 一、bat:替代cat,终端里的代码高亮神器 bat 是cat的平替升级版,核心解决了「终端看文件、看代码体验差」的痛点,我写Go代码、改OpenClaw配置文件,天天都在用。 1. 一键安装命令 MacOS: 1brew install bat Ubuntu/Debian: 1apt install bat CentOS/RHEL: 1dnf install bat 2. 3个我天天用的核心实战用法 用法1:直接查看文件,自带代码高亮+行号 这是最基础的用法,直接用bat 文件名替代cat 文件名,效果天差地别。 1bat main.go 不管是Go、Python、Shell代码,还是配置文件,都能自动识别语法,高亮显示,还自带行号,而且支持自动分页,并不会像cat一样全部输出出来,看代码再也不用瞎找行。 用法2:显示git修改状态,改了哪里一眼看清 bat会自动识别git仓库,对比文件的修改记录,新增、删除的内容会直接标出来,不用再敲git diff。 如果不想打开 IDE,那么用bat看一下git的修改记录,改了哪里一眼就能看清,巨方便。 用法3:配合搜索,精准定位内容 bat同样可以支持管道操作,配合rg、grep使用,搜索关键词,直接高亮显示匹配的内容,长文件里找东西,不用再翻半天。 1bat openclaw.json | grep "minimax" 用bash的用户,把.zshrc换成.bashrc就行。 二、eza:替代ls,终端文件列表神器 eza是ls的平替升级版,该项目是 exa 的fork维护版本,后者没有维护了。它解决了「ls输出乱、找不到文件、信息不全」的痛点,是我打开终端第一个要敲的命令。 1. 一键安装命令 MacOS: 1brew install eza Ubuntu/Debian: 1apt install eza CentOS/RHEL: 1dnf install eza 2. 3个高频实战用法,我已经设成了永久别名 用法1:基础列表,带图标+颜色,一眼分清文件类型 直接用eza替代ls,输出自带图标、颜色,文件夹、文件、可执行文件、压缩包,用不同颜色区分,再也不用黑白里找文件。 ...

2026-03-12 · 1 min · 115 words · 老墨

不会用飞书?没关系,粉丝要的腾讯家族,他来了!手把手教你接入QQ、企业微信到OpenClaw

大家好,我是极客老墨。 之前好几个[找我安装OpenClaw]({ « ref “content/posts/ai/20260305-为了搞个 AI 助手,我在 Windows 上折腾了整整一天.md” » })的粉丝(小白😄)一直给我诉苦: 飞书我不会啊,一会儿企业、一会儿机器人,门槛太高了! 老提示没权限,飞书这权限怎么搞啊,一天了连个文档都没给我创建起! 老墨,你不是说可以帮我写文案、搞表格啊?怎么还要我复制? …… 搞得老墨一阵尴尬!作为宠粉的我,哪能仍受你们受这种痛苦! 之前早都看到 Github早就支持了 QQ和企业微信,这两天刚好又看到腾讯官方也支持接入OpenClaw,这简直是福音啊,必须安排上! 说干就干,当天晚上就开始折腾。QQ 端 3 分半钟跑通,企业微信 10 分钟搞定。现在我的小龙虾 AI 已经住进 QQ 里了,手机随时能调用。 今天把整个过程记录下来,包括我踩过的所有坑。 为什么我要折腾这个? 说实话,之前国内的几大办公大佬中,只有飞书率先官方支持OpenClaw,所以飞书当然是当前的主流!但粉丝的需求让我意识到几个问题: 问题1:飞书门槛确实高 很多小白根本没听过飞书这东西,跟别提用了! 问题2:QQ/企业微信才是国民级应用 腾讯看家的东西,你我都懂,国民级应用。谁手机里没有 QQ?哪个公司不用企业微信?这才是真正的刚需。 所以,这两个,老墨必须给他干明白!废话不多说,正式开搞! 我先试了 QQ,结果 3 分半钟就跑通了 前置条件 你已经部署好了 OpenClaw(大模型已经配置好,能够启动并且状态正常) 你有一个已经登录的QQ 没有准备好的,可以看我的 [Mac上安装]({« rel “content/posts/ai/20260207-ep02_install.md” »}) 和 [Windows 上安装]({« rel “content/posts/ai/20260305-为了搞个 AI 助手,我在 Windows 上折腾了整整一天.md»})。 创建机器人:比想象中简单 打开 https://q.qq.com,要求你登录,直接扫码登录就对了,这里不多赘述。 直接向下看,你应该看到了这个“龙虾专用入口”的通道,点击“去使用”: 点「创建机器人」,一键自动就给你创建好了,AppID、AppSecret 直接给你,没那么多弯弯绕绕,贴心!当然,给你的默认信息你可以修改: ...

2026-03-11 · 2 min · 233 words · 老墨

OpenClaw这一套自动化流程下来,再也不需要我自己手动发图文了

大家好,我是极客老墨。 前几天折腾了一整天,终于把"每日一Go"的自动化流程跑通了。说实话,搞完的那一刻,我坐在椅子上发了会儿呆——这玩意儿真的能自己干活了? 让我从头说说这事儿。 起因:不想每天手动发图 做公众号的都知道,每天发图文是个体力活。选题、写题、生成代码图、上传素材、创建草稿、发布……一套下来,半小时没了。 我就在想:能不能让小墨(我的智能AI助理)自己干这事儿? 说干就干。 整个系统长这样 先给你看个整体架构图,不然光说容易晕: [题库] → [小墨] → [daily-go-skill] → [图片生成] ↓ [content-publisher] → [微信公众号] 三个核心组件: 1. daily-go-skill 小墨自己写的 skill,专门用来生成 Go 题目的图片。用 Python 实现的,代码高亮直接用现成的库,简单粗暴。 2. content-publisher 这是我自己搞的微信 API 中转服务。为啥要搞这个?因为微信的 appid 和 appsecret 太敏感了,直接放小墨那儿不安全。这个服务跑在我自己的服务器上,小墨只能通过 API 调用,拿不到敏感信息。 3. 题库 小墨自己维护的,用飞书的多维表格。字段包括题目、答案、难度、状态、日期等。后期题库大了可以直接上数据库,但现在飞书够用了。 小墨的工作流程 我给你还原一下它"脑子里"想什么。 第一步:接任务 我说:“小墨,发一道每日一Go到公众号。” 小墨的第一反应:“好的,老大。我先看看题库里有什么。” 第二步:读题库 小墨打开飞书表格,扫了一遍未发布的题目。 [题库查询] - 题目1:Go 的 defer 执行顺序?(已发布) - 题目2:slice 和 array 的区别?(已发布) - 题目3:map 是并发安全的吗?(未发布)✓ “找到了,就这道吧。” 第三步:生成图片 小墨调用 daily-go-skill,把题目传过去。 ...

2026-03-09 · 2 min · 250 words · 老墨

重启 OpenClaw,结果崩溃了!

大家好,我是极客老墨。 刚才手贱,把电脑重启了一下。再启动 OpenClaw,结果,起不来了,直接报错。 说实话,那一刻我有点懵。明明之前还好好的,怎么重启一下就挂了? 赶紧检查一下状态: $ openclaw health 直接出错: 看错误信息,大概是少了一个模块,@larksuiteoapi/node-sdk。 我第一反应是:这是飞书插件依赖的 SDK 吧? 记得之前启动的时候,总会提示两个飞书插件: 既然 .openclaw 下有一个飞书插件,怎么安装目录下还有一个呢? 难怪冲突。所以我之前是直接把 .openclaw 下的删除了。 现在想想,删错了。 到官方文档看看这个错误信息,找到了: 大概明白了,@larksuiteoapi/node-sdk 这个库没有兼容最新版本的 OpenClaw。 看来是 OpenClaw 安装包下的插件太旧了,应该删除它才对。 直接删了: 然后重新安装飞书插件: $ openclaw plugins install feishu 或者重新发起配置流程,会自动安装这个插件: $ openclaw config 虽然还是有那个 plugins.allow 的错误,但是终于可以成功跑起来了…… 老墨总结 折腾完这个 bug,我有几个感悟: 1. 快速迭代是把双刃剑 OpenClaw 更新太快了,这事儿有好有坏。 好处是功能迭代快、问题修复及时。但坏处是——插件生态跟不上。 2. 备份意识要加强 这次是插件冲突,删错了还能重装。但如果是配置文件丢了、数据坏了呢? 3. 插件生态需要跟上 OpenClaw 发展这么快,插件作者也得跟上节奏。 不然用户每次更新都可能踩坑,体验就差了。 ...

2026-03-09 · 1 min · 63 words · 老墨

GPT-5.4 来了,全球风靡的 OpenClaw 还能养多久?

大家好,我是极客老墨。 今天我们不聊技术,咱们聊聊 AI 助手的未来。 早上(3月7日)刷到 36氪那条新闻 的时候,我正在用 OpenClaw 帮我整理昨天的代码笔记。标题很刺眼:“GPT-5.4 发布,OpenClaw 的能力要被替代?” 我愣了一下,然后笑了。 不是因为觉得 OpenClaw 真的会被替代,而是这种"替代论"太熟悉了。去年这个时候,大家还在讨论 Copilot 会不会让程序员失业;今年,轮到了 OpenClaw。 说实话,GPT-5.4 的发布确实让我挺激动的。不是看新闻激动,而是因为我之前在 Cursor 里体验过它。 那种体验,怎么说呢? 就是你写一个函数,它不仅能理解你想干什么,还能主动帮你重构,把那种"能跑但有点丑"的代码,改成"看起来像是精心设计的"样子。有一次我写了个数据处理脚本,逻辑有点绕,它直接给我重写成管道式处理,还附带了注释:“这样更容易测试”。 我当时盯着屏幕看了三秒钟,然后默默删掉了自己原来的代码。 这就是 GPT-5.4 的代码能力。不是简单的补全,是真的在理解你的意图,然后给出更好的方案。 但今天的重点不是这个。 让我真正思考的是另一件事:GPT-5.4 原生支持桌面操作了。 根据那篇文章,OpenAI 把 CUA(Computer Use Ability)直接整合进了模型。这意味着什么?意味着 GPT-5.4 可以直接操作你的电脑,打开浏览器、点击界面、发送邮件、安排日历——基本上就是 OpenClaw 现在做的事,但它是原生的。 相比于 5.2, OSWorld Verified 的测试成绩从 47.3% 提升到 75%,BrowseComp 从 65.8% 提升到 82.7%。 OSWorld Verified: AI 在真实操作系统环境中完成 开放式任务 的能力。 BrowseComp: AI 在 浏览器环境 中完成复杂任务的能力。 数字很枯燥,但背后的意思很清楚:OpenAI 正式入场"AI 操作电脑"这个赛道了。 ...

2026-03-07 · 1 min · 145 words · 老墨

AI 龙虾第一天就给我写了个程序,还会定时爬数据

大家好,我是极客老墨。 经过昨天的艰难跋涉(详见上篇),OpenClaw 终于在 Windows 上跑起来了。第一天使用下来,说实话,这玩意儿比我想象的能干。 让它帮我写代码:经典贪吃蛇游戏 作为技术控,第一个尝试必须是代码!我还是让 小墨(我的AI助理) 帮我写一个经典的贪吃蛇网页游戏。 我直接告诉他:“小墨,帮我写一个贪吃蛇的网页游戏,最后文件直接发给我”。结果他没反应,后来才知道,原来他正卖力的写代码去了,顾不上理我,因为现在数据还没有能力流式返回,必须等他写完了给我我才知道(晚上的「飞书龙虾会直播」我看了,已经有流式返回数据的插件了,后边研究了再来讲) 写完之后,他并没有直接给我文件,而是问我怎么给我这个文件。这里应该是我的提示词没优化好,第一次配合,有点翻车。 再次确认让他发给我,这次就对了。打开游戏一看——界面还不错,完成度100%。就是速度太快了,我玩了几次才适应过来。 第二个任务:自动搜索小红书爆款文案 我的想法很简单:让 AI 助手自动搜索小红书上某个类目的爆款视频标题和内容。 OpenClaw 的做法很硬核——直接本地打开浏览器,像真人一样操作。这也是它完成工作的主要方式:模拟真人操作电脑。 让他搜索小红书,结果告诉我要登录: 登录后,他成功打开浏览器并返回了数据给我: 再次让他搜索 OpenClaw: 这是他打开的网页: 他能够读取网页并返回准确的数据回来。 第三个任务:每日早报功能 我很关注 AI 领域的新闻,所以想让小墨每天定时给我报告最新的新闻和 AI 资讯。我直接告诉他: 1你好,小墨。整理下边的信息发给我 21. 获取今天成都天气 32. 获取今日微博全球新闻榜前10条,标题为微博新闻 43. 获取今日最新的AI相关的新闻资讯前10条,标题为AI咨询 54. 整理成自带链接可以直接点击的简洁文字内容发给我 一开始 prompt 忘记加链接了,补上后,他返回了正确的数据。同样地,他会打开本地浏览器,输入网址自己看,最后返回正确的数据: 最后,我需要每天定时给我,他成功帮我搞定: 一个小插曲,他干完活浏览器没关闭,我提醒他之后他认识到了自己的错误。 可以看到,gateway 控制页面上的“定时任务”菜单中查看,已经加上了这个任务: 晚上看了飞书玩虾大会,几点感悟 晚上看了飞书的玩虾大会直播,看大牛们都是怎么玩龙虾的,有几点感悟: 1. 自动化办公真的来了 飞书与 OpenClaw 集成之后,抓数据、做报表、整理知识库这些日常工作都可以交给 OpenClaw 了。 2. 多只龙虾协同配合 多个龙虾搞到群组中,一起协同配合,形成工作流。这比传统的工作流工具方便得多,而且更智能。龙虾之间在不同的工程中可以互相促进,并变得更智能。 ...

2026-03-06 · 1 min · 166 words · 老墨

为了搞个 AI 助手,我在 Windows 上折腾了整整一天

大家好,我是极客老墨。 说实话,macOS 上装 OpenClaw 那叫一个丝滑,一行命令下去,半小时搞定全部配置。 结果换到 Windows,我心态崩了。 PowerShell 版本不对、权限配置踩坑、飞书机器人配置更是绕得我头晕。整整两天,我差点把电脑砸了。 但搞完之后,我发现 Windows 部署其实没那么难,只是网上没人把坑说清楚。 今天老墨把踩过的坑全抖出来,看完你半小时就能搞定。 我的 Mac 是办公电脑,但我想搞个"永不关机"的 AI 助手 之前(2月份)写了一篇 在 macOS 安装 OpenClaw 的文章,当时国内 IM 应用还没支持。没想到一个月过去,飞书、钉钉、企业微信、QQ 居然全都支持了,更新速度之快! 我的 Mac 是办公电脑,不能 24 小时跑着玩。但我又想要一个随时能召唤的 AI 助手,怎么办? 翻出角落里落灰的 Windows PC,性能没得说,就是有点费电😭。没办法,Mac Mini 一机难求啊,各位有没有 mini PC 可以推荐的? 言归正传,咱们开始。 前置检查:PowerShell 版本这个坑,90% 的人会踩 装之前,先看看你的 PowerShell 版本。低于 7.0,后面装依赖会报错到你怀疑人生。 怎么查?打开 PowerShell(不是 CMD!),输入: 1$PSVersionTable.PSVersion 如果显示的是 5.1,恭喜你,中奖了。 必须升级到 7,不然装 OpenClaw 的时候会报各种奇奇怪怪的错。 升级方法简单到离谱: 打开微软应用商店 搜索 “PowerShell” 点击安装 装完之后,一定要用新打开的 PowerShell 7 窗口,别用原来的旧窗口。 ...

2026-03-05 · 2 min · 391 words · 老墨

Go这样检测Data Race让并发程序又简单了许多

大家好,我是极客老墨。 写并发代码最怕什么?不是死锁,不是性能问题,而是那些偶尔才出现、难以复现的诡异 bug。这些 bug 的罪魁祸首往往就是 Data Race(数据竞争)。 什么是 Data Race? Data Race 的定义很简单:当两个或多个 goroutine 同时访问同一个变量,且至少有一个在写入时,就会发生数据竞争。 这就像两个人同时在一张纸上写字,最后的结果可能是谁的字都不完整,或者干脆就是一团乱码。 举个例子: 1var counter int 2 3func increment() { 4 counter++ // 看起来是一行代码,实际上是三个操作:读取、加1、写入 5} 6 7func main() { 8 go increment() 9 go increment() 10 time.Sleep(time.Second) 11 fmt.Println(counter) // 结果可能是 1,也可能是 2 12} 这段代码看起来没问题,但 counter++ 并不是原子操作。两个 goroutine 可能同时读到 0,然后都写入 1,最终结果就是 1 而不是期望的 2。 顺便说一句,Data Race 和 Race Condition 是两个不同的概念,很多人会混淆。我之前翻译过一篇文章专门讨论这个问题,感兴趣可以看看。 Go 内存模型定义了 Happens-Before 规则来保证程序执行的顺序性,这和 Java 内存模型的思路类似。但光有规则还不够,Go 从 1.1 版本开始提供了 Race Detector,让我们能在运行时检测出这些问题。 ...

2026-03-03 · 5 min · 870 words · 老墨

2026年2月:AI狂飙的一个月,老墨的技术探索之旅

2026年2月:AI狂飙的一个月,老墨的技术探索之旅 大家好,我是极客老墨。 2026年的2月,对我来说是个特别的月份。不仅因为这是马年春节,更因为这一个月我完成了一次彻底的技术转型——从一个传统的后端开发者,变成了一个AI时代的"全栈玩家"。 这个月我发布了多篇技术文章,涵盖AI、Golang、工具开发等多个领域。有些在我的博客上已经发布几年了,这月拿出来重新整理和更新了,有些则是老墨一字字重新码起来的。熟悉我的读者都知道,老墨秉持的风格就是简洁、高效,只讲干货、不讲废话! 本月自公众号恢复更新以来,粉丝数量和阅读量都有非常明显的增长,非常感谢大家的关注🙏!今天就来盘点一下这个月的收获,也给关注我的朋友们一个交代。 一、AI实战系列:从入门到精通的完整路径 这个月最大的主题就是AI。从本地部署到智能体开发,我用4篇连载文章(EP00-EP03)带大家走完了整个流程。 EP00:DeepSeek R1本地部署实战 核心内容:手把手教你在Mac上用Ollama部署DeepSeek R1,实现完全免费、隐私安全的本地AI环境。 技术亮点: 详细的模型选择指南(1.5B到32B全覆盖) 性能优化技巧(Flash Attention + KV Cache量化) 接入VS Code实现免费的Copilot替代方案 读者反馈:“终于不用担心代码泄露了,本地跑DeepSeek真香!” EP01:为什么选OpenClaw做数字管家 核心观点:OpenClaw不是简单的聊天工具,而是一个运行在本地的AI Gateway,能让你躺在床上用手机指挥电脑干活。 深度解析: OpenClaw的架构设计(Gateway模式) 与Claude Desktop的本质区别 创始人Peter Steinberger的传奇故事(卖掉公司1亿欧元后重新创业) 核心价值:“Your assistant. Your machine. Your rules.” —— 这才是真正的私人AI助理。 EP02:安装和搭建OpenClaw 实战指南:从零开始配置OpenClaw,接入Telegram/Discord,实现远程控制。 踩坑经验: Telegram配置的完整流程(包括Pairing机制) Discord的复杂权限设置 网络代理的关键配置 读者评价:“跟着教程一步步来,半小时就搞定了!” EP03:OpenClaw的Docker监狱 安全第一:如何用Docker隔离OpenClaw,防止AI"越狱"搞破坏。 技术深度: 文件系统隔离策略 网络白名单配置 官方Sandbox机制详解 从Native迁移到Docker的完整方案 核心理念:“永远不要信任你的Agent” —— 这是极客生存法则第一条。 大模型实战指南(2026新春版) 全面对比:国内外主流大模型的深度评测,从GPT-5到DeepSeek V4,从Claude 4到通义千问Qwen 3。 评分体系: 复杂任务成功率 推理深度与幻觉率 智能体交互体验 核心结论: GPT-5 Omni:深度推理天花板(9.9分) Claude 4 Opus:拟人化与长记忆之王(9.8分) Qwen 3:国内综合最强六边形战士(9.7分) DeepSeek V4:代码性价比之王(9.6分) 选型建议:“组合使用,才是2026年最高效的AI生存之道” ...

2026-02-27 · 2 min · 272 words · 老墨

Go模糊测试实战:让AI帮你找Bug

大家好,我是极客老墨。 之前写过一篇Go 测试:写得爽,跑得快,聊了单元测试、表格驱动测试和基准测试。很多朋友留言问:“老墨,单元测试我会写了,但总感觉测不全,怎么办?” 这就是今天要聊的:模糊测试(Fuzzing)。 写代码爽,写测试累。更累的是,你辛辛苦苦写了一堆单元测试,覆盖率看起来挺高,结果上线还是出Bug。为啥?因为你只测了你能想到的场景,没测你想不到的。 Go 1.18 开始,官方直接把模糊测试内置了。它会自动生成海量随机输入,帮你找到那些"想不到的 Bug"。不需要装第三方库,go test 就能跑。 什么是模糊测试?一句话说清楚 模糊测试就是让程序自己生成各种奇葩输入,疯狂测试你的代码,直到找出Bug为止。 传统单元测试:你说测什么就测什么。 模糊测试:程序自己想办法搞你,直到搞出问题。 从Go 1.18开始,Go官方在标准库里内置了Fuzzing支持。不需要装第三方工具,开箱即用。 为什么要用模糊测试? 老墨先给你看个真实案例。 假设你写了个URL解析函数,单元测试写了10个case,都通过了。结果上线后,用户输入了一个带emoji的URL,程序直接panic。 为什么?因为你的测试用例里没有emoji。 单元测试的局限性: 只能测你想到的场景 边缘情况容易遗漏 维护成本高(每个case都要手写) 模糊测试的优势: 自动生成海量测试数据 能发现你想不到的边缘情况 特别擅长找安全漏洞(SQL注入、缓冲区溢出等) 实战案例:一个看似简单的字符串反转函数 我们来写个最简单的函数:反转字符串。 1func Reverse(s string) string { 2 b := []byte(s) 3 for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 { 4 b[i], b[j] = b[j], b[i] 5 } 6 return string(b) 7} 看起来没问题吧?我们写个单元测试: 1func TestReverse(t *testing.T) { 2 testcases := []struct { 3 in, want string 4 }{ 5 {"Hello, world", "dlrow ,olleH"}, 6 {" ", " "}, 7 {"!12345", "54321!"}, 8 } 9 for _, tc := range testcases { 10 rev := Reverse(tc.in) 11 if rev != tc.want { 12 t.Errorf("Reverse: %q, want %q", rev, tc.want) 13 } 14 } 15} 运行测试: ...

2026-02-26 · 5 min · 936 words · 老墨

Go模块管理踩坑实录:从入门到放弃再到真香

大家好,我是老墨。 很早之前,翻译了一篇 Golang 官方的模块管理文档中文,那篇有点“官方”了,今天老墨搞一篇接地气的模块实战教程。 说实话,我第一次接触 Go 模块的时候,内心是拒绝的。 GOPATH 那套虽然老,但好歹能用。突然冒出来个 go.mod、go.sum,还有什么 replace、indirect,看着就头大。更别提那些看起来像乱码的版本号:v1.2.3-0.20210101000000-abcdef123456,这是什么鬼? 但折腾了几个月后,我发现:真香。 今天就把我踩过的坑、学到的经验,全部掏出来。不讲理论,只讲实战。 为什么要用Go模块? 先说说为什么要用模块,而不是继续用GOPATH。 GOPATH 的三大痛点 版本管理是灾难 所有依赖都在 $GOPATH/src 下 同一个包只能有一个版本 项目 A 用 v1.0,项目 B 用 v2.0?抱歉,做不到 依赖地狱 依赖的依赖的依赖……谁也不知道用了什么版本 换台机器编译?祝你好运 团队协作?每个人的依赖都不一样 无法复现构建 今天能编译,明天就不行了 因为依赖包更新了 你根本不知道 Go 模块解决了什么 每个项目独立管理依赖 版本号明确,可复现构建 依赖关系清晰,记录在 go.mod 校验和保证安全性(go.sum) 支持私有仓库和代理 快速上手:5分钟创建第一个模块 1. 初始化模块 1# 创建项目目录 2mkdir myproject 3cd myproject 4 5# 初始化模块 6go mod init github.com/yourusername/myproject 这会生成一个go.mod文件: 1module github.com/yourusername/myproject 2 3go 1.21 就这么简单。 2. 添加依赖 写点代码,导入个包: 1package main 2 3import ( 4 "fmt" 5 "github.com/gin-gonic/gin" 6) 7 8func main() { 9 r := gin.Default() 10 r.GET("/", func(c *gin.Context) { 11 c.JSON(200, gin.H{"message": "Hello"}) 12 }) 13 r.Run() 14} 然后: ...

2026-02-25 · 4 min · 751 words · 老墨

2026 年了,这些 AI IDE 还能白嫖

大家好,我是极客老墨。 去年这个时候,我还在纠结要不要订阅 Cursor Pro。今年,我的电脑里装了七八个 AI IDE,一个月下来,花的钱是零。 不是我抠门,是这些工具的免费额度真的够用。写个脚本、改改 Bug、重构代码,基本不用掏钱。当然,如果你是重度用户,每天写几千行代码,那该付费还是得付费。但对于大部分开发者来说,薅羊毛的空间还是很大的。 下面这些工具是我这段时间用下来觉得值得折腾的,有些需要科学上网,有些需要改地区,有些直接某宝买个 Key 就能用。别问我怎么搞,懂的都懂。 为什么需要 AI IDE? 说实话,刚开始我也觉得 AI 写代码是噱头。直到有一次我要写个 Python 脚本处理 JSON 数据,平时可能要查半天文档,结果 Cursor 直接给我生成了,改都不用改。 回想一下,2025 年初的时候,GitHub Copilot 还只能做行内补全,写个函数名它给你补全函数体,仅此而已。那会儿大家还在惊叹"哇,AI 能写代码了"。结果到了 2025 年中,Cursor 出来了,直接能多文件编辑,Cmd+K 一下改十几个文件。再到 2025 年底,Claude 3.5 Sonnet 发布,上下文窗口直接干到 200K,能理解整个项目的代码逻辑。 现在 2026 年初,这才过了一年,AI IDE 已经卷到什么程度了?Windsurf 免费无限补全,Kiro 支持本地模型和自定义工作流,Antigravity 能看懂设计稿直接生成 UI 代码。一年前你还在为 Copilot 的 10 刀月费纠结,现在免费工具多到用不过来。 更夸张的是模型本身的进化速度。GPT-4 刚出来的时候,写个复杂算法还经常出 Bug。现在 Claude 3.5 Sonnet 和 GPT-4 Turbo,不仅能写代码,还能做代码审查、重构、写测试、解释架构。去年你还在担心 AI 会不会抢饭碗,今年你已经在担心不用 AI 会不会被淘汰。 这个速度真的有点吓人,而且仍然再以肉眼可见的速度飞速发展。谁能想到,在2026年初,你只要描述你的需求,AI就能直接给你生成完整的、能够直接运行的前后端代码,包括 UI 设计都能给你实现!虽然编写大型代码还存在一定的问题,但是老墨大胆预测一下,2026年将迎来 AI 齐头并进、百花争艳的盛况! ...

2026-02-23 · 5 min · 969 words · 老墨

2026马年春节,我用AI帮我写了一个嘴替小程序

春节回家,最怕的是什么?不是堵车,不是抢票,而是亲戚的灵魂拷问。 “工资多少啊?” “有对象了吗?” “什么时候买房?” 今年我决定不再被动挨打,用3天时间撸了个"春节嘴替"小程序,让AI帮我练习怼人。更重要的是,整个开发过程几乎全靠AI完成——从产品设计到代码实现,我只是个"提示词工程师"。 先看效果 小程序叫"春节嘴替",核心功能有三个: AI嘴替对话 - 和虚拟亲戚battle,练习高情商回怼 妈妈银行存单 - 生成趣味压岁钱对账单 马年开运头像 - 制作春节专属头像 神仙祝福 - AI生成个性化拜年祝福语 最有意思的是AI嘴替功能。我设计了4个经典角色: 势利眼二姨(儿子阿里P8,逢人就炫) 催婚大姑(见面就问对象) 凡尔赛邻居(女儿在国外"留学") 严肃二舅(体制内,看不起互联网打工人) 每个角色都有完整的人设和攻击策略,AI会根据你的回复动态调整战斗力。如果你怼得好,AI会破防;如果你怼得不够狠,AI会继续压制你。 你可以体验一下,看看效果: AI开发全流程 这个项目最大的特点是:几乎全部由AI来完成。 1. 产品设计:Google AI Studio + Gemini 3.0 Pro Preview 我先把需求丢给Gemini: “我想做一个春节主题的小程序,帮年轻人应对亲戚的尬聊。你帮我设计产品方案。” Gemini给出了完整的PRD文档,包括: 目标用户画像 核心功能定义 技术架构建议 上架物料清单 开发时间表 这份文档直接成为了我的开发指南。AI不仅帮我理清了思路,还提醒我注意内容合规、类目审核等坑点。 2. UI素材:Nano Banana Pro(图片生成) 小程序需要大量视觉素材:角色头像、背景图、装饰元素等。我全部用Google AI Studio的图片生成模型搞定。 典型的Prompt: A cute 3D cartoon Chinese aunt character, wearing red traditional clothes, holding a smartphone, slightly snobbish expression, pop mart style, bright red background, Chinese New Year atmosphere, 8k 生成的图片质量很高,直接就能用。关键是速度快,几秒钟就能出图,比找设计师或自己画快太多了。 ...

2026-02-20 · 2 min · 335 words · 老墨

大模型实战指南(2026年新春版):深度推理与智能体的时代

大模型实战指南(2026年新春版):深度推理与智能体的时代 发布背景速览 2026年初,各大AI公司密集发布新一代大模型,标志着大模型技术进入新的发展阶段: Google Gemini 3.0:2026年1月发布,重点强化多模态能力,在图像和视频处理方面实现重大突破,成为多模态领域的领跑者。 ChatGPT 5.3:OpenAI在2025年底推出GPT-5 Omni后,2026年初发布5.3版本,进一步优化深度推理能力,在复杂逻辑推导方面保持领先。 Claude Opus 4.6:Anthropic在2026年初推出4.6系列,特别针对代码生成和智能体场景进行深度优化,成为开发者的首选工具。 国内模型:阿里通义千问Qwen 3、百度文心一言4.0、字节Seedance 2.0等均在2026年初完成重要更新,在中文理解和特定领域应用上实现突破。 最近被朋友问麻了:“老墨,都2026年了,GPT-5到底值不值那个天价订阅费?” “Claude 4的超长记忆真的不丢包了吗?” “国产模型现在的‘推理能力’是不是真的赶上来了?” 作为一个AI探索的老鸟,我想说:2026年的大模型之战,已经从“生成内容”变成了“解决复杂问题”。 如果说两年前大家还在惊叹AI能写诗、画图,那么现在,如果你不能帮我自主完成一个跨应用的工作流,或者进行长达半小时的深度逻辑推导,那你都不好意思叫顶尖模型。 今天老墨就把压箱底的实测经验掏出来,从实战角度对比国内外主流大模型(2026版)。不玩虚的排行榜,只聊“怎么用、在哪用、值不值得用”。 注意,这里老墨只列出正式发布的、老墨实际使用过的、能够直接上生产环境用的大模型版本,其他的还没有正式发布的版本比如 gpt-5.3-turbo-preview、 Google Gemini 3.0 Pro Preview 等暂不考虑。 此外,评分都是根据老墨的个人使用经验,难免有疏漏。如果有任何错误或建议,请在评论区留言。 先搞懂3个问题:老墨不说废话(2026年版) 1. 大模型现在进化到哪一步了? 别再只盯着“多模态”看了,那在2024年底就已经是标配了。2026年的关键词是: 深度推理(System 2 Thinking):模型不再是只会快思考的“鹦鹉”,它们现在能像人类一样慢下来,进行多步规划、自我反思和纠错。这是解决数学难题和复杂编程的关键。 智能体化(Agents):模型不再是一个等着你提问的聊天框,而是能主动操控浏览器、终端、为你点外卖、订机票的“数字员工”。 记忆与个性化:顶尖模型现在能记住你几个月前说过的话,真正成为了懂你的私人助理,而不是每次都要重新介绍背景。 2. 老墨的评分凭什么? 两年前的那些MMLU基准早就不够看了。现在老墨主要看: 复杂任务成功率:扔给它一个模糊的商业需求,它能不能拆解并最终交付结果? 推理深度与幻觉率:在长链条逻辑推导中,它会不会自己把自己绕晕?(现在幻觉已经大幅降低,但依然存在)。 智能体交互体验:它调用外部工具(如搜索、代码解释器、第三方API)顺不顺手? 3. 国内外模型现在的真实差距? 老墨结论(2026年版): 顶尖战力:OpenAI和Anthropic依然在**“深度推理”和“通用智能体架构”**上引领方向,属于“定义未来”的角色。 多模态王者:Google的Gemini在图像和视频处理方面已经超越ChatGPT,在多模态领域处于绝对领先地位。 中国速度:国内头部大厂(阿里、字节、百度、深求)在应用层打磨得极好,在**特定领域(如中文语境下的复杂任务、性价比代码生成)**已经完全不输甚至局部领先国际巨头, 但是,在图像图像处理、视频处理等多模态领域,仍然有明显的差距。 格局:以前是仰望,现在是慢慢看齐,中国大模型正在以非常快的速度追赶。 国际主流大模型:探索智能上限的先行者 1. GPT-5 Omni (OpenAI) —— 深度推理的绝对王者 最新动态:2025年底发布的重磅炸弹,目前(2026年初)公认的战力天花板。 核心能力:革命性的“慢思考”模式(System 2)。遇到难题时,你会看到它显示“正在规划思路…”,然后进行长达数十步的自我推导和验证。此外,它的原生全模态(视频/音频/文本无缝实时流转)体验极其丝滑。 老墨评价:它是拿来解决你解决不了的问题的。 如果你只是写个邮件,用它属于杀鸡用牛刀。但如果你要设计复杂的软件架构、推导前沿数学猜想,或者需要一个能真正理解视频内容并和你实时语音辩论的AI,GPT-5 Omni是唯一选择。贵是真贵,强是真强。 适合场景:前沿科研、复杂系统设计、需要极高逻辑密度的任务、实时视频/语音深度交互。 老墨评分:9.9分(扣0.1分是因为价格和偶尔的过度思考) 在 Cursor 最新版本中早就支持了 GPT 5.3 版本,老墨也在测试中,目前来看,这是目前地球上写代码最强的“大脑”, 与 Claude Opus 4.6 有点旗鼓相当的味道了,代码理解能力超强,这里暂不赘述,后续在考虑撰文来评测一番。 ...

2026-02-15 · 2 min · 294 words · 老墨

[GoLang避坑实战-00] 别再犹豫了!一个老码农的真心话:为什么 2026 年必学 Go

大家好,我是极客老墨。 在互联网行业摸爬滚打十几年,从早期的LAMP时代,到Java的Spring全家桶,再到后来的云原生浪潮,老墨见证了技术的每一次变迁。 最近很多朋友问我:“老墨,现在的AI写代码这么厉害,大模型日新月异,智能IDE更是百花齐放,Claude Code、Cursor 简直是神,还需要专门去学一门后端语言吗?如果学,学什么比较好?Java还是Python?或者 Rust?” 这是一个非常好的问题。今天老墨就结合自己的经验,跟大家聊聊为什么在这个AI横行的时代,我依然建议大家掌握Golang,并且会手把手教你如何拿下它。 1. AI时代,为什么还要学后端语言? 很多同学觉得,现在AI不仅能生成CRUD代码,甚至能帮你完成全套编码、测试,这样的发展速度,我真的有必要学编程?我只需要会写Prompt不就行了吗? 大错特错。 AI确实能提高效率,但它目前还无法替代架构思维和底层认知。 知其然,更要知其所以然:AI生成的代码,如果出了Bug,或者性能不达标,你看不懂怎么调优? 不仅仅是Coder,更是Engineer:单纯的写代码(Coding)会被AI取代,但工程化能力(Engineering)——包括系统设计、并发处理、错误治理、服务部署,是AI很难完全掌控的。 掌握控制权:作为一名开发者,你不能做AI的傀儡。你需要有能力判断AI生成的代码是垃圾还是金子。 掌握一门强类型的、编译型的后端语言,能让你深入理解计算机的工作原理:内存管理、进程线程、网络协议。这些内功,是Prompt Engineering给不了你的。 2. 为什么是Golang? 在众多后端语言中,老墨首推 Go (Golang)。不是因为赶时髦,而是基于实用主义的考量。 Java:沉重的企业级战车 Java确实强大,生态无敌,将近30年了仍然是一门非常活跃的开发语言。但是: 太重了:Spring Boot启动一下,内存吃掉几百兆是常事。对于想要快速开发微服务或者云原生应用的极客来说,有点“大炮打蚊子”。 语法繁琐:虽然有了Lombok和新版本的语法糖,而且语法糖、新特性在一直增加,就是为了简化开发、提高效率,但比起Go的简洁,Java依然显得啰嗦。 JVM调优是玄学:GC调优、JVM参数配置,是一门高深的学问,对于初学者来说门槛较高。 Rust:陡峭的绝壁 Rust绝对是好语言,内存安全,性能极致。但是: 学习曲线太陡峭:所有权(Ownership)、借用(Borrowing)、生命周期(Lifetime),这些概念能劝退90%的初学者。老墨到现在仍然还在学习 Rust,深有体会! 开发效率:为了通过编译器的检查,你可能需要花费大量时间与编译器搏斗。对于大多数互联网业务应用来说,Rust的开发效率不如Go。 Golang:平衡的艺术 Go语言是Google出品,有着纯正的工程血统。 简单直接:只有25个关键字(早期),语法极其简洁,没有花哨的语法糖。任何Go程序员写的代码,风格都惊人的一致,这在团队协作中是巨大的优势。 天生并发:go func(),一个Goroutine开启并发,Channel进行通信。这是我见过的处理并发最优雅的方式,没有之一。 性能强悍:编译型语言,接近C/C++的性能,但开发效率接近Python。 云原生通用语:Docker、Kubernetes、Prometheus…这些云原生时代的基石,全是Go写的。学了Go,你就拿到了通往云原生世界的门票。 老墨总结: 如果你想快速构建高性能的后端服务,不想被复杂的语法和繁重的运行时拖累,Go是你的不二之选。 3. 极客老墨的Golang学习路径 很多同学这就去买书了,别急!听老墨一句劝:不要一开始就啃大骨头! 现代语言学习,讲究的是 “Learn by Doing”。 第一阶段:不仅是Syntax,更是思维转变(1-2周) 不要死记硬背语法。重点理解Go独特的概念: 接口(Interface):Duck Typing(鸭子模型),非侵入式接口,这和Java的implements完全不同。 Goroutine & Channel:不要用共享内存来通信,要用通信来共享内存。这是Go并发的核心哲学。 Defer & Panic:Go没有try-catch,适应它的错误处理机制。 第二阶段:标准库是最好的老师(2-3周) Go的标准库(Standard Library)写得非常漂亮。重点攻克: net/http:几行代码起一个Web Server。 fmt, io, bufio:理解IO操作。 encoding/json:JSON处理是后端日常。 context:重中之重! 并发控制、超时处理全靠它。 第三阶段:工程化实战(1个月) 光会写Hello World没用,你需要能干活的框架: ...

2026-02-10 · 1 min · 179 words · 老墨

EP03 - 试图驯服一只猛兽:OpenClaw 的 Docker 监狱

EP03 - 试图驯服一只猛兽:OpenClaw 的 Docker 监狱 昨天我在测试 OpenClaw 的 “File System Tool” 时,发生了一件让我冷汗直流的事。 我给它的指令是:“清理一下当前目录的临时文件”。 可能是因为我的 Prompt 写得太随意,它直接把我的整个 ~/Downloads 文件夹给清空了。 幸好那里只有一堆没用的安装包。但如果它删的是我的 ~/Documents 或者 .ssh 密钥呢? 这就是我们在 link:/posts/ai/why-i-choose-openclaw/[] 里提到的代价:强大的能力伴随着巨大的风险。 一个拥有 Shell 权限的 AI Agent,本质上就是一个盯着你键盘随时准备按下回车键的超级用户。在裸机 (Bare Metal) 上直接运行它,无异于把自家大门的钥匙交给一个喝醉了的锁匠。 今天,我们要给这个不仅聪明而且危险的家伙,造一座牢不可破的监狱。 这也是极客生存法则第一条:永远不要信任你的 Agent。 本文涉及本地Native安装的一些知识,你可以阅读上一篇 EP02 来了解它们。 为什么一定要用 Docker? 很多人觉得 Docker 只是为了方便部署。 错。对于 Agent 开发来说,Docker 是保命符。 文件隔离 (Filesystem Isolation): 把它关在 /app 里。它想 rm -rf /?请便,删的只是容器里的文件,我的 Mac 毫发无损。 网络白名单 (Network Whitelist): 我们可以限制它只能访问特定的 API,防止它把我的本地数据传给不知名的服务器。 环境一致性: 你不用担心 Node.js 版本不对,或者缺了什么 Python 库。 手把手构建 “The Cage” 我们不需要什么花哨的 k8s,一个简单的 Dockerfile 和 docker-compose.yml 就够了。 但这里有几个针对 Mac 用户和 Ollama 的关键坑,我都替你踩平了。 ...

2026-02-09 · 3 min · 562 words · 老墨

EP02 - 给你的电脑安装一个数字管家: 安装和搭建 OpenClaw

EP02 - 给你的电脑安装一个数字管家: 安装和搭建 OpenClaw 安装 OpenClaw 暂时抛开官方这些“深奥”的建议,我们先来尝试安装它,下一篇我们再来安全地把它关到“沙盒”中去。安装 OpenClaw 非常简单,只需要一行命令: 1# macos 2curl -fsSL https://openclaw.bot/install.sh | bash 3 4# windows 5iwr -useb https://openclaw.ai/install.ps1 | iex 但是过程中有许多注意的地方,以我的mac为例,我们来看一下安装过程。 终端执行上述命令后,会先检查一些依赖,比如node, npm等,如果你的电脑没有安装,需要先安装: 稍作等待,安装完成后,OpenClaw 会自动执行 OpenClaw doctor 命令,来诊断环境和安装是否正确。 由于我之前安装了旧版的 clawdbot, 它会自动迁移配置信息。 doctor 执行完成后,会自动进行设置,此时会弹出诸多选项让你做出选择,比如配置模型、配置聊天客户端、Skills 等。 最终完成会输出访问的url(默认是 http://127.0.0.1:18789), 就可以在浏览器中打开控制台(dashboard)了. 接入 Telegram (首选) 为什么首选 Telegram? 很遗憾,国内的IM目前 OpenClaw 都不支持, 对于飞书、钉钉的支持据说还在开发中,所以目前我们最好的方式就是选择 Telegram. 因为它是这个星球上对开发者最友好的 IM,没有之一: 不需要手机号验证码,不需要营业执照,不需要企业认证。 你只需要跟一个叫 @BotFather 的机器人聊两句,就能拿到通往 OpenClaw 的钥匙。 极客配置三步走: 搞定 Token 打开 Telegram,搜索 @BotFather (认准蓝标)。 发送指令 /newbot。 给你的机器人起个名字 (比如 MyJarvis) 和用户名 (必须以 bot 结尾,比如 my_jarvis_bot)。 BotFather 会给你一串红色的 Token,长得像这样:123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11。复制它 切勿外传,否则可能别人就帮你操作你的电脑了! 配置 OpenClaw 如果你之前已经设置了telegram,那么直接下一步。没有配置,则推荐使用交互式配置: 1openclaw channels add 选择 Telegram,然后粘贴 Token。 或者直接修改配置文件 ~/.openclaw/openclaw.json (更 Geek 的方式,不推荐,除非你完全掌握了OpenClaw): 1"channels": { 2 "telegram": { 3 "token": "YOUR_TOKEN_HERE" 4 } 5} 验证与配对 (Pairing) 在 Telegram 里找到你的机器人,点击 Start 或发送 /start。 ...

2026-02-07 · 2 min · 336 words · 老墨

EP01 - 告别 Claude Desktop:为什么我选 OpenClaw 做我的数字管家

EP01 - 告别 Claude Desktop:为什么我选 OpenClaw 做我的数字管家 To be honest,Claude Desktop 发布 MCP (Model Context Protocol) 的时候,我是真的很兴奋。 但那种兴奋只维持了三天。 当你真正试图把 AI 融入工作流时,你会发现 Claude Desktop 有两个致命死穴: 它被困在桌面上:你必须坐在电脑前才能用它。 它是闭源的黑盒:你不知道它怎么处理你的文件,每一条指令都像在把家门钥匙交给那个坐在旧金山办公室的陌生人。 直到我发现了 OpenClaw。 Not Just a Bot, It’s a Gateway 先澄清一个误区:OpenClaw 不仅仅是一个像 Chatbox 那样的“套壳客户端”。 它的本质是一个运行在你本地的 Gateway(网关)。 如果你读过它的官方文档,你会发现它的架构非常 Sex: WhatsApp / Telegram <-> Gateway (Localhost) <-> AI Agent (Tools) 这意味着什么? 这意味着你可以躺在床上用手机发消息(支持 WhatsApp/Telegram 等等几十种聊天客户端),指挥你书房里的 Mac 干重活。 这也是它的口号: “The AI that actually does things.” 我想这也是为什么他会这么大火的原因。 场景 A: 周末出门在外,服务器突然报警。你不用掏出电脑连热点 SSH,直接在 Telegram 里发一句:“查一下 Nginx 为什么挂了”。OpenClaw 帮你跑 tail -f /var/log/nginx/error.log 并总结给你。 场景 B: 路上突然想到一个 Idea。发给 OpenClaw:“记到我的 Obsidian 里,并同步到 GitHub”。它直接操作你本地的文件系统。 你可以到这里看看大家都在用 OpenClaw 做些什么:https://openclaw.ai/showcase ...

2026-02-05 · 2 min · 249 words · 老墨

为了学 AI,我用 Go + Fyne 手撸了一个原生视频下载器

为了学 AI,我用 Go + Fyne 手撸了一个原生视频下载器 大家好,我是老墨。 一个写了十几年代码,发际线依然坚挺,但最近确实有点焦虑的中年程序员。 为啥焦虑?还不是因为这该死的 AI。 从 24 年初 Sora 横空出世,到 Claude 3.5 杀疯了,再到最近 DeepSeek 甚至能自己修 Bug,这世界变化快得像开了二倍速。以前我们卷算法、卷架构,现在倒好,不仅要卷提示词,还要防着被自己的 IDE 抢了饭碗。 老墨我痛定思痛,觉得不能坐以待毙。打不过就加入嘛,我也开始疯狂恶补 AI 知识。 01 一个悲伤的故事:学习资料太多也是一种烦恼 要学 AI,最好的路径是什么?这年头文档更新赶不上模型迭代,最鲜活的知识全在视频里。YouTube 上的 Andrej Karpathy 大神课,B 站上的各种论文精读、实战通过… 于是我的浏览器收藏夹很快就爆了。 但我这人有个毛病,看视频喜欢囤。一来是有些干货太硬,得反复咀嚼;二来是作为技术人,总有一种由于网络不确定性带来的"松鼠症"——好东西必须存在本地硬盘里才踏实。 这时候问题来了:市面上的下载器,怎么就没一个顺手的? 某雷:广告多是其次,关键是我想下的 YouTube 和 B 站视频它基本都解析不了。对于我们这种想看外网 AI 前沿教程的人来说,它形同虚设。 某 IDM:嗅探功能很强,但碰到 YouTube/B 站 这种音视频分离的高画质(DASH流)视频就歇菜了,经常只能下个无声画面,心累。而且 Mac 上没有原生版,体验割裂。 Electron 系工具:界面是好看了,但这动不动几百兆的内存占用,我开个 PyCharm 跑模型本来就捉襟见肘,哪还有余粮养它们? 命令行 yt-dlp:这是真神,功能无敌。用了很久,自己写脚本都写了多个。但时间久了脚本太多,每次想下个视频还得敲命令,复制粘贴 URL,还得拼代理参数… 实在是比较麻烦。 “求人不如求己,为什么不能自己写一个GUI?” 这念头一出,我就兴奋了。写了几年的 Golang,这点事情不在话下。说干就干,开搞开搞。 02 技术选型:要做就做原生的 既然决定自己干,那必须立好 Flag: 要快:启动快,下载快。 要美:虽然我是后端出身,但也不能忍受丑陋的 UI。 要轻:拒绝 Electron,拒绝 WebView,我要纯原生。 基于这个标准,技术栈基本就锁死了: ...

2026-02-04 · 2 min · 360 words · 老墨

EP00 - DeepSeek R1 本地部署实战 (Mac篇)

EP00 - DeepSeek R1 本地部署实战 (Mac篇) 摘要: 别被几万块的显卡劝退。你的 MacBook Pro (Apple Silicon) 就是跑 DeepSeek R1 的神器。本文手把手教你用 Ollama 在本地跑起“满血版”推理模型,不仅免费,而且隐私绝对安全。 阅读时间: 5分钟 适用人群: 程序员、科研党、隐私敏感用户 硬件要求: M1/M2/M3/M4 Mac,推荐 16GB+ 内存 为什么要在本地跑 DeepSeek? 隐私安全: 你的代码、私有文档不需要上传到云端,断网也能用。 零延迟响应: 没有网络延迟,交互更丝滑(取决于模型大小)。 无审查: 你懂的。 免费: 不需要订阅费,只消耗电费。 核心工具:Ollama Ollama 是目前 macOS 上体验最好的大模型运行工具,没有之一。它开源免费,支持非常多的大模型,GitHub仓库在 这里, 目前161K的 Star。 用程序员最能听懂的话解释:Ollama 就是大模型界的 Docker。 Docker 让你可以一行命令跑 MySQL / Nginx。 Ollama 让你可以一行命令跑 DeepSeek / Llama3。 它在后台默默做了三件事:驱动 GPU、管理模型文件、提供 API 服务。装了它,你的 Mac 就有了“大脑”。 安装 Ollama 有两种方式: 方式 A: 官网下载 (推荐小白) 访问 ollama.com 下载 macOS 版本并安装。 ...

2026-02-03 · 2 min · 422 words · 老墨

Go高级教程:反射 (Reflection) 实战

大家好,我是极客老墨。 反射 (Reflection) 赋予了程序在运行时检查和修改自身状态的能力。从 JSON 解析到 ORM 框架(如 GORM),再到依赖注入,它们的底层都离不开反射。有人说"反射是魔鬼",也有人说"没有反射,就没有现代 Web 框架"。这篇就带你拆解 reflect 包,学会如何在运行时动态操作对象。 1. 核心概念:Type 和 Value 在 reflect 包中,有两位绝对主角: reflect.Type:这是啥?(类型信息,如 int, string, User) reflect.Value:这值多少?(具体的数据,如 42, “hello”, User{Name:“Hank”}) 一切反射操作的起点都是 interface{}。 1package main 2 3import ( 4 "fmt" 5 "reflect" 6) 7 8func main() { 9 x := 3.14 10 11 // 1. 获取类型 12 t := reflect.TypeOf(x) 13 fmt.Println("Type:", t) // float64 14 15 // 2. 获取值 16 v := reflect.ValueOf(x) 17 fmt.Println("Value:", v) // 3.14 18} graph LR subgraph iface ["interface{}"] direction TB TypePtr["_type pointer"] DataPtr["data pointer"] end TypePtr -->|"reflect.TypeOf"| RType["reflect.Type"] DataPtr -->|"reflect.ValueOf"| RValue["reflect.Value"] style iface fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#333 style TypePtr fill:#e1f5fe,stroke:#01579b,color:#01579b style DataPtr fill:#e1f5fe,stroke:#01579b,color:#01579b style RType fill:#fff9c4,stroke:#fbc02d,color:#333 style RValue fill:#fff9c4,stroke:#fbc02d,color:#333 2. 三大反射定律 Go 的反射有三条铁律(出自 Rob Pike): ...

2026-01-28 · 3 min · 485 words · 老墨

Go高级教程:其他并发工具

大家好,我是极客老墨。 Goroutine 和 Channel 是 Go 并发的基础,但有些场景它们不够用。频繁创建对象导致 GC 压力大?用 sync.Pool。并发读写 map 会 panic?用 sync.Map。简单的计数器用锁太重?用 atomic。 这篇就聊聊 Go 的高级并发工具,看看它们各自适合什么场景。 sync.Pool:对象复用 频繁创建和销毁大对象会给 GC 带来压力,sync.Pool 用于对象复用。 基本用法 1package main 2 3import ( 4 "fmt" 5 "sync" 6) 7 8// 创建对象池 9var bufferPool = sync.Pool{ 10 // New 函数:池为空时创建新对象 11 New: func() interface{} { 12 fmt.Println("Creating new buffer") 13 return make([]byte, 1024) 14 }, 15} 16 17func main() { 18 // 从池中获取对象 19 buf := bufferPool.Get().([]byte) 20 21 // 使用对象 22 copy(buf, []byte("Hello, World!")) 23 fmt.Println(string(buf[:13])) 24 25 // 归还到池中 26 bufferPool.Put(buf) 27 28 // 再次获取(会复用刚才的对象) 29 buf2 := bufferPool.Get().([]byte) 30 fmt.Println(string(buf2[:13])) // 还是 "Hello, World!" 31} 要点: ...

2025-12-20 · 9 min · 1831 words · 老墨

Go高级教程:深入理解 GMP 调度器

大家好,我是极客老墨。 为什么 Go 语言能轻松支撑百万并发?为什么 Goroutine 切换成本这么低?这一切的背后,都站着一位神秘的大管家——GMP 调度器。这篇就用通俗易懂的语言,配合生动的比喻,带你深入理解 Go 高并发的核心秘密。 1. 为什么需要 GMP? 在很久很久以前(其实也就几十年前),我们写代码都是直接跟 线程 (Thread) 打交道。线程是操作系统(OS)调度的最小单位。 但是,线程这玩意儿太"贵"了: 内存占用高:一个线程栈大概要几 MB。 切换成本大:线程切换需要陷入内核态,保存寄存器、上下文,这简直就是"劳民伤财"。 这时候,Go 语言的设计师们拍案而起:“我们要造一种更轻量的线程!” 于是,Goroutine (协程) 诞生了。它初始只要几 KB,切换成本极低。 这就带来了一个问题:操作系统只认识线程,不认识 Goroutine。谁来负责把成千上万个 Goroutine 分配给 CPU 跑呢? 这就需要一个"中间商" —— Go 运行时调度器 (Scheduler)。 图示: Thread 与 Goroutine 的区别 2. GMP 模型大揭秘 GMP 其实是三个角色的缩写: G (Goroutine):我们写的代码任务,也就是协程。 M (Machine):工作线程(Thread),对应操作系统的真实线程。它是真正的干活人(搬砖工)。 P (Processor):逻辑处理器(Context),可以理解为"调度上下文"或"资源"。它是包工头,负责管理 G,并把 G 交给 M 去执行。 形象的比喻 想象一个大型搬砖工地: G (砖头):待搬运的任务。 M (工人):负责搬砖的劳动力。 P (手推车):工人必须推着车才能搬砖(因为车里装着搬砖工具和任务清单)。 如果没有 P(手推车),M(工人)就不知道该干啥。 ...

2025-11-28 · 2 min · 364 words · 老墨

[GoLang避坑实战-18] 实战 CLI 工具:30 分钟用 urfave/cli 构建你的极客武器

大家好,我是极客老墨。 上一篇我们写了个小工具——一个简单的命令行计算器,需要从命令行读参数, 我们直接通过 os.Args 来解析命令行参数,这种方式太底层、太原始了,非常容易出错。 说实话,Go 虽然内置了 flag 包,但用起来仍然不是很方便。想要做个像样的 CLI 工具,光靠 flag 远远不够。 今天我们就来聊聊如何用 Go 社区最受欢迎的 urfave/cli 框架,打造真正的极客工具。 为什么不用 flag 包? Go 标准库的 flag 包确实能解析参数,但问题是: 1// 用 flag 包写个简单的工具 2var name = flag.String("name", "", "your name") 3var age = flag.Int("age", 0, "your age") 4 5flag.Parse() 6 7if *name == "" { 8 fmt.Println("name is required") 9 os.Exit(1) 10} 看到没?每个参数都要手动验证,帮助信息要自己拼,子命令?不存在的,得自己实现。 写个小工具还行,但要做个像 git、docker 那样的专业 CLI,光靠 flag 真的会疯。 CLI 框架选择 Go 生态里有几个流行的 CLI 框架: cobra:Kubernetes、Hugo 都在用,功能强大但有点重 urfave/cli:轻量、简单、够用,老墨的首选 survey:专门做交互式 CLI 的,适合问答式工具 今天主要聊 urfave/cli,因为它: ...

2025-11-20 · 7 min · 1448 words · 老墨

[GoLang避坑实战-17] 从零撸一个计算器:基础知识的大合练

大家好,我是极客老墨。 学了这么多语法,是时候写个完整项目了。光看代码片段,很难理解 Go 项目是怎么组织的。这篇我们从零开始,写一个命令行工具,看看真实项目的结构和开发流程。 项目目标 我们要做一个简单的命令行计算器,支持基本的数学运算。 功能需求 支持加减乘除运算 命令行参数输入 彩色输出结果 错误处理 技术要点 Go Modules 依赖管理 标准项目结构 包的导入和使用 第三方库集成 交叉编译 初始化项目 第一步是创建项目目录并初始化模块。 创建项目 1cd go-tutorial-code 2# 创建项目目录 3mkdir 15-project-example 4cd 15-project-example 5 6# 初始化 Go Module 7go mod init github.com/hankmor/calc 这里老墨为了教程的需要,把代码放到了 go-tutorial-code, 并且模块名称没有与文件夹名称一致。 生成的 go.mod: 1module github.com/hankmor/calc 2 3go 1.24 要点: 模块名通常是代码仓库地址 go.mod 是项目的起点 初始化后就可以开始写代码了 项目结构 Go 项目有约定俗成的目录结构。 标准布局 15-project-example/ ├── go.mod # 模块定义 ├── go.sum # 依赖校验 ├── cmd/ # 命令行工具目录 │ └── calc/ │ └── main.go ├── pkg/ # 可导出的库代码 │ └── calculator/ │ ├── calculator.go │ └── calculator_test.go ├── internal/ # 私有代码(不可被外部导入) │ └── utils/ │ └── helper.go ├── README.md # 项目说明 └── Makefile # 构建脚本(可选) 目录说明: ...

2025-11-10 · 8 min · 1512 words · 老墨

[GoLang避坑实战-16] "干净利落!手把手带你搭一套企业级 Go 项目目录

大家好,我是极客老墨。 好的项目结构能让代码更易理解、易维护、易扩展。Go 社区有一套被广泛认可的标准布局,这篇就聊聊如何组织 Go 项目,让代码结构清晰合理。 1. 标准项目布局 参考:golang-standards/project-layout 1myproject/ 2├── cmd/ # 主程序入口 3│ ├── server/ 4│ │ └── main.go # 服务端入口 5│ └── cli/ 6│ └── main.go # 命令行工具入口 7├── internal/ # 私有代码(不可被外部导入) 8│ ├── handler/ 9│ ├── service/ 10│ └── repository/ 11├── pkg/ # 公共库(可被外部导入) 12│ ├── util/ 13│ └── validator/ 14├── api/ # API 定义(OpenAPI/Swagger) 15│ └── openapi.yaml 16├── web/ # Web 静态资源 17│ ├── static/ 18│ └── templates/ 19├── configs/ # 配置文件 20│ ├── config.yaml 21│ └── config.prod.yaml 22├── scripts/ # 脚本(构建、部署等) 23│ ├── build.sh 24│ └── deploy.sh 25├── test/ # 额外的测试数据 26│ └── fixtures/ 27├── docs/ # 文档 28│ └── API.md 29├── go.mod 30├── go.sum 31├── Makefile # 构建脚本 32├── Dockerfile 33└── README.md 2. 核心目录详解 2.1 cmd/ 存放主程序入口,每个子目录对应一个可执行文件。 ...

2025-10-29 · 8 min · 1597 words · 老墨

环境变量与配置管理

大家好,我是极客老墨。 同一份代码需要在开发、测试、生产等不同环境运行。配置管理让我们能够灵活切换环境,而不需要修改代码。这篇就聊聊 Go 的配置管理,从最简单的环境变量到强大的 Viper 库。 1. 环境变量基础 1.1 读取环境变量 1import ( 2 "fmt" 3 "os" 4) 5 6func main() { 7 // 读取环境变量 8 dbHost := os.Getenv("DB_HOST") 9 if dbHost == "" { 10 dbHost = "localhost" // 默认值 11 } 12 13 fmt.Println("DB Host:", dbHost) 14 15 // 检查环境变量是否存在 16 port, exists := os.LookupEnv("PORT") 17 if !exists { 18 port = "8080" 19 } 20} 1.2 设置环境变量 1// 在程序中设置(仅影响当前进程) 2os.Setenv("API_KEY", "secret123") 3 4// 在 shell 中设置 5// export DB_HOST=localhost 6// export DB_PORT=3306 2. godotenv:.env 文件 2.1 安装 1go get -u github.com/joho/godotenv 2.2 使用 创建 .env 文件: ...

2025-09-11 · 4 min · 670 words · 老墨

模糊测试入门 (Fuzzing)

大家好,我是极客老墨。 单元测试只能测试你想到的情况。空字符串、负数、超长输入,这些边界情况很容易遗漏。模糊测试能自动生成大量随机输入,帮你发现那些没想到的 Bug。 这篇就聊聊 Go 的模糊测试,看看它是怎么帮我们提升代码质量的。 什么是模糊测试 模糊测试(Fuzzing)是一种自动化测试技术,通过生成随机输入来发现程序的异常。 核心思想 1// 传统单元测试:测试已知输入 2func TestAdd(t *testing.T) { 3 if Add(2, 3) != 5 { 4 t.Error("2 + 3 should be 5") 5 } 6 if Add(0, 0) != 0 { 7 t.Error("0 + 0 should be 0") 8 } 9} 10 11// 模糊测试:测试大量随机输入 12func FuzzAdd(f *testing.F) { 13 f.Fuzz(func(t *testing.T, a, b int) { 14 result := Add(a, b) 15 // 检查属性而不是具体值 16 if a > 0 && b > 0 && result <= 0 { 17 t.Errorf("Add(%d, %d) = %d, overflow?", a, b, result) 18 } 19 }) 20} 区别: 单元测试:测试已知的输入和输出 模糊测试:测试未知的输入,检查程序属性 能发现什么问题 崩溃和 panic:空指针、数组越界 整数溢出:加法、乘法溢出 无限循环:特定输入导致死循环 内存泄漏:特定输入导致内存不释放 逻辑错误:边界条件处理不当 编写第一个 Fuzz 测试 从一个简单的例子开始。 被测函数 1// reverse.go 2package stringutil 3 4// Reverse 反转字符串 5func Reverse(s string) string { 6 b := []byte(s) 7 for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { 8 b[i], b[j] = b[j], b[i] 9 } 10 return string(b) 11} Fuzz 测试 1// reverse_test.go 2package stringutil 3 4import "testing" 5 6func FuzzReverse(f *testing.F) { 7 // 添加种子输入(可选) 8 f.Add("hello") 9 f.Add("世界") 10 f.Add("") 11 12 // Fuzz 函数 13 f.Fuzz(func(t *testing.T, s string) { 14 // 属性:反转两次应该等于原字符串 15 reversed := Reverse(s) 16 doubleReversed := Reverse(reversed) 17 18 if s != doubleReversed { 19 t.Errorf("Reverse(Reverse(%q)) = %q, want %q", 20 s, doubleReversed, s) 21 } 22 }) 23} 要点: ...

2025-08-20 · 8 min · 1540 words · 老墨

日志管理:从 log 到 zap

大家好,我是极客老墨。 排查问题时,日志是第一手段。服务挂了、请求慢了、数据错了,都要靠日志定位。但日志写不好,要么找不到关键信息,要么性能拖累整个系统。 这篇就聊聊 Go 的日志管理,从标准库到高性能方案,看看怎么写好日志。 标准库 log Go 自带的 log 包,简单但功能有限。 基本使用 1package main 2 3import ( 4 "log" 5) 6 7func main() { 8 // 基本输出 9 log.Println("Server started") 10 11 // 格式化输出 12 log.Printf("User %s logged in", "admin") 13 14 // 带前缀 15 log.SetPrefix("[MyApp] ") 16 log.Println("With prefix") 17 18 // 输出:[MyApp] 2025/07/18 10:00:00 With prefix 19} 要点: log.Println 输出日志 log.Printf 格式化输出 log.SetPrefix 设置前缀 默认输出到 stderr 自定义 Logger 1import ( 2 "log" 3 "os" 4) 5 6func main() { 7 // 创建自定义 Logger 8 logger := log.New( 9 os.Stdout, // 输出目标 10 "[INFO] ", // 前缀 11 log.Ldate|log.Ltime|log.Lshortfile, // 标志 12 ) 13 14 logger.Println("Custom logger message") 15 // 输出:[INFO] 2025/07/18 10:00:00 main.go:15: Custom logger message 16} 标志选项: ...

2025-07-18 · 7 min · 1425 words · 老墨

[GoLang避坑实战-11] 万物皆 Reader:像老鸟一样玩转 Go 语言的 IO 流

大家好,我是极客老墨。 在很多语言里,文件操作和网络操作是两套完全不同的 API。但在 Go 语言里,无论你是读文件、读网络请求,还是读一段内存字符串,你面对的通常都是同一个东西:io.Reader。 这种**“万物皆 Reader”**的设计哲学,是 Go 语言简洁高效的灵魂所在。这篇我们就来深度拆解 Go 的 IO 体系,让你不仅会用,还能写出高性能的 IO 代码。 1. 核心基石:Reader 与 Writer 接口 Go 的 IO 体系建立在两个极简的接口之上: 1type Reader interface { 2 Read(p []byte) (n int, err error) 3} 4 5type Writer interface { 6 Write(p []byte) (n int, err error) 7} 为什么这两个接口牛逼? 因为它屏蔽了底层实现。只要一个类型实现了 Read 方法,它就是 Reader。你可以把一个“读取文件”的 Reader 直接传给一个“处理 HTTP 响应”的函数。 老墨避坑指南: Read 方法里有个细节——io.EOF。它表示“文件结束”,虽然它是一个 error 类型,但在逻辑处理中,它通常标志着读取成功的终点,而不是程序出错了。所以处理时要先看 n > 0 处理数据,再看 err == io.EOF 退出循环。 2. 基础操作:从一次性到流式 2.1 小文件:一键读写 如果你处理的是几 MB 以内的配置文件,os.ReadFile 和 os.WriteFile 是最爽的: 1data, err := os.ReadFile("config.yaml") // 一次性进内存 2if err != nil { /* 处理错误 */ } 3 4err = os.WriteFile("backup.yaml", data, 0644) 2.2 大文件:流式读取(避开 OOM) 处理 GB 级别的日志,你绝不能一次性读取。得用“流”的方式: ...

2025-06-19 · 2 min · 371 words · 老墨

[GoLang避坑实战-15] Go Module 救命指南:彻底告别依赖冲突和环境混乱

大家好,我是极客老墨。 Go 1.11 之前,依赖管理是个大麻烦。GOPATH 要求所有代码放在固定目录,vendor 目录管理混乱,dep 工具又不够成熟。Go Modules 的出现彻底解决了这些问题,现在已经是官方标准方案。 这篇就聊聊 Go Modules,看看它是怎么管理依赖的。 Go Modules 是什么 Go Modules 是 Go 的依赖管理系统,解决了三个核心问题。 核心功能 1// go.mod 文件示例 2module github.com/username/myproject 3 4go 1.21 5 6require ( 7 github.com/gin-gonic/gin v1.9.1 8 gorm.io/gorm v1.25.5 9) 解决的问题: 版本管理:明确指定每个依赖的版本 可重现构建:不同环境构建结果一致 依赖隔离:不同项目可以使用同一个包的不同版本 与 GOPATH 的区别 1# GOPATH 时代(痛苦) 2export GOPATH=$HOME/go 3cd $GOPATH/src/github.com/username/project 4# 所有项目共享依赖,版本冲突频繁 5 6# Go Modules 时代(简单) 7mkdir myproject 8cd myproject 9go mod init github.com/username/myproject 10# 每个项目独立管理依赖 初始化模块 创建新项目时,第一步就是初始化模块。 ...

2025-06-06 · 9 min · 1878 words · 老墨

GoLang教程——泛型编程入门

大家好,我是极客老墨。 Go 1.18 之前,写一个通用的 Min 函数很麻烦。想支持 int 和 float64?要么写两遍代码,要么用 interface{} 加反射,性能还差。其他语言早就有泛型了,Go 终于在 1.18 补上了这个短板。 这篇就聊聊 Go 的泛型,看看它是怎么让代码更通用、更安全的。 为什么需要泛型 没有泛型时,写通用代码很痛苦。 问题:重复代码 1// 比较两个 int 2func MinInt(a, b int) int { 3 if a < b { 4 return a 5 } 6 return b 7} 8 9// 比较两个 float64(重复代码) 10func MinFloat64(a, b float64) float64 { 11 if a < b { 12 return a 13 } 14 return b 15} 16 17// 比较两个 string(又是重复) 18func MinString(a, b string) string { 19 if a < b { 20 return a 21 } 22 return b 23} 问题:interface{} 不安全 1// 使用 interface{} 可以接受任何类型 2func Min(a, b interface{}) interface{} { 3 // 需要类型断言,容易出错 4 // 而且失去了类型安全 5} 解决方案:泛型 1// 一个函数支持多种类型 2func Min[T int | float64 | string](a, b T) T { 3 if a < b { 4 return a 5 } 6 return b 7} 泛型函数 泛型函数使用类型参数,可以处理多种类型。 ...

2025-03-22 · 9 min · 1814 words · 老墨

GoLang教程——Context上下文实战

大家好,我是极客老墨。 写并发程序时,经常遇到这样的场景:用户关闭了浏览器,但后台的数据库查询还在跑;API 调用超时了,但 Goroutine 还在等待响应。这些"失控"的 Goroutine 会浪费资源,甚至导致内存泄漏。 Go 的 Context 就是用来解决这个问题的。它能控制 Goroutine 的生命周期,实现超时、取消和数据传递。 这篇就聊聊 Context 的核心用法,看看它是怎么管理并发任务的。 Context 是什么 Context 是一个接口,定义了四个方法: 1type Context interface { 2 Deadline() (deadline time.Time, ok bool) 3 Done() <-chan struct{} 4 Err() error 5 Value(key interface{}) interface{} 6} 核心功能: 取消信号:通知 Goroutine 停止工作 超时控制:限制任务执行时间 数据传递:在调用链中传递元数据 创建 Context Go 提供了几个函数来创建 Context。 Background 和 TODO 1import "context" 2 3// Background:根 Context,通常在 main 函数中使用 4ctx := context.Background() 5 6// TODO:当不确定用什么 Context 时使用 7ctx := context.TODO() 要点: Background 是最顶层的 Context TODO 用于占位,表示还没想好用什么 两者都不会被取消,没有超时,没有值 WithCancel:手动取消 1// 创建可取消的 Context 2ctx, cancel := context.WithCancel(context.Background()) 3 4// 调用 cancel 取消 Context 5cancel() WithTimeout:超时自动取消 1// 2 秒后自动取消 2ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 3defer cancel() WithDeadline:指定截止时间 1// 指定截止时间 2deadline := time.Now().Add(2 * time.Second) 3ctx, cancel := context.WithDeadline(context.Background(), deadline) 4defer cancel() WithValue:传递数据 1// 存储键值对 2ctx := context.WithValue(context.Background(), "userID", 123) 3 4// 获取值 5if userID, ok := ctx.Value("userID").(int); ok { 6 fmt.Println("User ID:", userID) 7} 超时控制 超时控制是 Context 最常用的场景。 ...

2025-03-02 · 7 min · 1481 words · 老墨

[GoLang避坑实战-14] 写得爽,跑得快:表格驱动测试的工程级避坑实战

大家好,我是极客老墨! 刚转 Go 的时候,我还在找"Go 版的 JUnit 在哪"。结果发现,Go 根本不需要第三方测试框架,go test 命令 + testing 包就够了。 写测试不只是为了完成 KPI,更是为了**“让自己晚上能睡个好觉”**。这篇我们不仅聊基础,更要聊聊在大厂工程实践中,如何写出既稳健又专业的测试。 Go中的测试,不是"一等公民",是"超等公民"。Go 编译器自带 go test 工具,标准库提供 testing 包,写测试简单到爆。 1. 单元测试:从入门到专业 Go 的测试文件规则很简单:以 _test.go 结尾,函数名以 Test 开头。 1.1 基础写法 1func TestAdd(t *testing.T) { 2 if got := Add(1, 2); got != 3 { 3 t.Errorf("Add(1, 2) = %d; want 3", got) 4 } 5} 跑一下: 1$ go test -v 2=== RUN TestAdd 3--- PASS: TestAdd (0.00s) 4PASS 5ok example.com/math 0.392s 就这么简单。不需要装任何库,不需要配置文件。 1.2 进阶:表格驱动(标配) 这是 Go 社区的灵魂写法。把数据和逻辑分离,增加测试用例只需在切片里加一行,这就是“表格驱动”。 1func TestAddTableDriven(t *testing.T) { 2 // 1. 定义测试用例 3 tests := []struct { 4 name string // 用例名称 5 a, b int // 输入 6 want int // 期望结果 7 }{ 8 {"正数", 1, 2, 3}, 9 {"负数", -1, -2, -3}, 10 {"混合", -1, 1, 0}, 11 {"零", 0, 0, 0}, 12 } 13 14 // 2. 遍历执行 15 for _, tt := range tests { 16 // t.Run 启动子测试,方便定位错误 17 t.Run(tt.name, func(t *testing.T) { 18 got := Add(tt.a, tt.b) 19 if got != tt.want { 20 t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want) 21 } 22 }) 23 } 24} 为什么推荐表格驱动? ...

2025-02-19 · 3 min · 491 words · 老墨

[GoLang避坑实战-13] 标准库挖矿:这几个库包能让你少写一半代码

大家好,我是极客老墨。 Go 标准库是个宝藏。fmt 格式化输出、strings 字符串处理、time 时间操作、json 序列化、os 文件读写,这些日常开发 80% 的需求都能搞定,不用到处找第三方库。 今天精选几个最常用的包,看看它们怎么用,能解决什么问题。 fmt:格式化输入输出 fmt 包用于格式化输出和输入,是最常用的包之一。 基本输出 1import "fmt" 2 3func main() { 4 // 输出不换行 5 fmt.Print("Hello") 6 fmt.Print("World") 7 8 // 输出换行 9 fmt.Println("Hello") 10 fmt.Println("World") 11 12 // 格式化输出 13 name := "Go" 14 age := 15 15 fmt.Printf("Name: %s, Age: %d\n", name, age) 16} 常用占位符 1// 字符串 2fmt.Printf("%s\n", "hello") // hello 3fmt.Printf("%q\n", "hello") // "hello" (带引号) 4 5// 整数 6fmt.Printf("%d\n", 42) // 42 (十进制) 7fmt.Printf("%b\n", 42) // 101010 (二进制) 8fmt.Printf("%x\n", 42) // 2a (十六进制) 9 10// 浮点数 11fmt.Printf("%f\n", 3.14) // 3.140000 12fmt.Printf("%.2f\n", 3.14159) // 3.14 (保留2位) 13 14// 布尔值 15fmt.Printf("%t\n", true) // true 16 17// 通用 18fmt.Printf("%v\n", 42) // 42 (默认格式) 19fmt.Printf("%+v\n", struct{X int}{42}) // {X:42} (带字段名) 20fmt.Printf("%#v\n", []int{1,2}) // []int{1, 2} (Go语法) 21fmt.Printf("%T\n", 42) // int (类型) 格式化字符串 1// Sprintf 返回字符串 2msg := fmt.Sprintf("Hello, %s!", "World") 3fmt.Println(msg) // Hello, World! 4 5// Errorf 创建错误 6err := fmt.Errorf("failed to open file: %s", "config.json") 要点: ...

2025-01-12 · 14 min · 2878 words · 老墨

GoLang教程——并发进阶

大家好,我是极客老墨。 并发编程中,Channel 很好用,但不是万能的。有时候需要更精细的控制:等待一组任务完成、保护共享数据、限制并发数量。这时候就需要 sync 包的同步工具了。 这篇就聊聊 Go 的并发进阶工具,看看它们各自适合什么场景。 WaitGroup:等待组 WaitGroup 用于等待一组 Goroutine 完成,是最常用的同步工具。 基本用法 1import ( 2 "fmt" 3 "sync" 4 "time" 5) 6 7func worker(id int, wg *sync.WaitGroup) { 8 defer wg.Done() // 完成时调用 9 10 fmt.Printf("Worker %d starting\n", id) 11 time.Sleep(time.Second) 12 fmt.Printf("Worker %d done\n", id) 13} 14 15func main() { 16 var wg sync.WaitGroup 17 18 for i := 1; i <= 5; i++ { 19 wg.Add(1) // 增加计数 20 go worker(i, &wg) 21 } 22 23 wg.Wait() // 等待所有完成 24 fmt.Println("All workers completed") 25} 要点: Add(n) 增加计数器 Done() 减少计数器(等价于 Add(-1)) Wait() 阻塞直到计数器为 0 必须传递指针 *sync.WaitGroup 常见错误 1// ❌ 错误:在 goroutine 内部 Add 2go func() { 3 wg.Add(1) // 可能在 Wait 之后执行 4 defer wg.Done() 5 // ... 6}() 7 8// ✅ 正确:在启动前 Add 9wg.Add(1) 10go func() { 11 defer wg.Done() 12 // ... 13}() 批量添加 1func main() { 2 var wg sync.WaitGroup 3 4 // 一次性添加多个 5 wg.Add(5) 6 7 for i := 1; i <= 5; i++ { 8 go func(id int) { 9 defer wg.Done() 10 fmt.Printf("Task %d\n", id) 11 }(i) 12 } 13 14 wg.Wait() 15} Mutex:互斥锁 Mutex 用于保护共享数据,确保同一时间只有一个 Goroutine 访问。 ...

2024-12-05 · 9 min · 1714 words · 老墨

[GoLang避坑实战-12] 并发初体验:Goroutine 和 Channel 真的那么神吗?

大家好,我是极客老墨。 传统语言里写并发,要创建线程、加锁、处理竞态条件,一不小心就死锁。Go 的并发模型完全不同:用 Goroutine 代替线程,用 Channel 代替锁。这种 CSP(通信顺序进程)模型,让并发编程变得简单多了。 这篇就聊聊 Go 的并发基础,看看 Goroutine 和 Channel 是怎么配合工作的。 Goroutine 基础 Goroutine 是 Go 的轻量级协程,比线程轻量得多。 启动 Goroutine 使用 go 关键字启动一个 Goroutine。 1package main 2 3import ( 4 "fmt" 5 "time" 6) 7 8func sayHello() { 9 fmt.Println("Hello from goroutine") 10} 11 12func main() { 13 // 启动一个 Goroutine 14 go sayHello() 15 16 // 主 Goroutine 继续执行 17 fmt.Println("Hello from main") 18 19 // 等待一下,否则程序会立即退出 20 time.Sleep(time.Second) 21} 要点: go funcName() 启动一个新的 Goroutine Goroutine 是并发执行的,不会阻塞主程序 main 函数结束时,所有 Goroutine 都会被强制终止 Goroutine 的特点 1func printNumbers() { 2 for i := 1; i <= 5; i++ { 3 fmt.Printf("%d ", i) 4 time.Sleep(100 * time.Millisecond) 5 } 6} 7 8func printLetters() { 9 for i := 'A'; i <= 'E'; i++ { 10 fmt.Printf("%c ", i) 11 time.Sleep(100 * time.Millisecond) 12 } 13} 14 15func main() { 16 go printNumbers() 17 go printLetters() 18 19 time.Sleep(time.Second) 20 fmt.Println("\nDone") 21} 输出可能是:1 A 2 B 3 C 4 D 5 E 或其他顺序(不确定) ...

2024-11-19 · 13 min · 2588 words · 老墨

[GoLang避坑实战-10] 不要 panic!Go 风格错误处理的"优雅避坑"指南

大家好,我是极客老墨。 写代码时,错误处理往往占了一半的工作量。文件打不开、网络连不上、数据格式不对,这些都是常态。Go 的错误处理很直接:错误就是一个返回值,你必须显式检查它。 这篇就聊聊 Go 的错误处理机制,看看它是怎么让代码更健壮的。 错误是值 Go 的核心理念:错误是值(Errors are values),不是异常。 error 接口 Go 内置的 error 是一个接口,只有一个方法: 1type error interface { 2 Error() string 3} 任何实现了 Error() string 方法的类型都是 error。 创建错误 1import "errors" 2 3// 方式 1:使用 errors.New 4err1 := errors.New("something went wrong") 5 6// 方式 2:使用 fmt.Errorf(支持格式化) 7err2 := fmt.Errorf("failed to open file: %s", filename) 8 9// 方式 3:自定义错误类型 10type MyError struct { 11 Code int 12 Msg string 13} 14 15func (e MyError) Error() string { 16 return fmt.Sprintf("error %d: %s", e.Code, e.Msg) 17} 要点: ...

2024-10-29 · 10 min · 2103 words · 老墨

[GoLang避坑实战-09] 接口即正义:解密 Go 语言"鸭子类型"的实战精髓

大家好,我是极客老墨。 Go 没有类,也没有继承。它用结构体封装数据,用接口定义行为。这种"组合优于继承"的设计,让代码更灵活、更解耦。 这篇就聊聊 Go 的结构体和接口,看看它们是怎么配合工作的。 结构体基础 结构体是一组字段的集合,用来封装相关的数据。 定义和初始化 1// 定义结构体 2type Person struct { 3 Name string 4 Age int 5} 6 7// 初始化方式 1:按字段顺序 8p1 := Person{"Alice", 30} 9 10// 初始化方式 2:指定字段名(推荐) 11p2 := Person{ 12 Name: "Bob", 13 Age: 25, 14} 15 16// 初始化方式 3:部分字段(其余为零值) 17p3 := Person{Name: "Charlie"} // Age 为 0 18 19// 初始化方式 4:使用 new(返回指针) 20p4 := new(Person) 21p4.Name = "David" 要点: 字段名首字母大写表示导出(public),小写表示私有(package 内可见) 推荐使用字段名初始化,代码更清晰 未初始化的字段会被赋予零值 访问和修改字段 1p := Person{Name: "Alice", Age: 30} 2 3// 访问字段 4fmt.Println(p.Name) // Alice 5 6// 修改字段 7p.Age = 31 8 9// 指针访问(Go 会自动解引用) 10ptr := &p 11ptr.Name = "Alicia" // 等价于 (*ptr).Name = "Alicia" 匿名字段 结构体可以包含匿名字段(只有类型,没有字段名)。 ...

2024-09-15 · 9 min · 1710 words · 老墨

[GoLang避坑实战-08] 切片扩容的惨剧:为什么共享底层数组让你哭都哭不出来?

大家好,我是极客老墨。 写 Go 之前,我在 Java 里用 ArrayList、HashMap 这些集合类。转到 Go 发现只有数组、切片、map 三种,心想"这够用吗?" 结果发现 Go 的切片是动态数组,自动扩容,比 Java 的 ArrayList 还简洁。map 就是哈希表,但遍历顺序是随机的,这点要注意。 这篇就聊聊 Go 数组、切片和 map 的几个关键特性和几大隐蔽却常见的坑。 数组:长度固定,很少用 Go 的数组长度是类型的一部分,[3]int 和 [4]int 是不同类型: 1var arr [3]int = [3]int{10, 20, 30} 2fmt.Println(arr) // [10 20 30] 3 4// 让编译器推导长度 5arr2 := [...]int{1, 2, 3, 4, 5} ⚠️ 注意:数组是值类型,赋值和传参会复制整个数组。 数组的几种初始化方式 1// 指定索引初始化 2arr := [5]int{0: 10, 2: 20, 4: 30} // [10, 0, 20, 0, 30] 3 4// 部分初始化(其余为零值) 5arr2 := [5]int{1, 2} // [1, 2, 0, 0, 0] 6 7// 多维数组 8matrix := [2][3]int{ 9 {1, 2, 3}, 10 {4, 5, 6}, 11} 💡 技巧:实际开发中很少直接用数组,都用切片,因为比起固定长度,我们多数更需要的是动态扩容。 ...

2024-08-22 · 5 min · 1061 words · 老墨

[GoLang避坑实战-07] 扔掉冗长的 if-else:Go 流程控制的"极简之道"

大家好,我是极客老墨。 写 Go 之前,我在 Java 里循环都是 for、while、do-while 三件套。转到 Go 发现只有一个 for,心想"这够用吗?" 结果发现 Go 的 for 能当 while 用,能当无限循环用,还能用 range 遍历。更绝的是 switch 默认不用 break,if 还能带初始化语句。 这篇就聊聊 Go 控制结构的几个巧妙设计。 if:条件判断不用括号 Go 的 if 语句很简洁,条件不需要括号: 1x := 10 2if x > 5 { 3 fmt.Println("x is large") 4} 5 6// if-else 7if x > 10 { 8 fmt.Println("x is large") 9} else { 10 fmt.Println("x is small") 11} ⚠️ 注意:大括号 {} 是必须的,且左大括号不能换行。 初始化语句:限制变量作用域 if 可以带初始化语句,变量作用域仅限于 if-else 块: 1// 常用于错误处理 2if err := doSomething(); err != nil { 3 fmt.Println("Error:", err) 4 return 5} 6 7// 常用于 map 查找 8if value, ok := m["key"]; ok { 9 fmt.Println("Found:", value) 10} else { 11 fmt.Println("Not found") 12} 💡 技巧:这种写法让变量作用域更小,代码更简洁。 ...

2024-08-01 · 5 min · 1027 words · 老墨

[GoLang避坑实战-06] 该选指针接收者吗?老墨带你一次理清函数与方法的纠葛

大家好,我是极客老墨。 函数返回多个值?刚接触 Go 时我还不习惯,写惯了 Java 的单返回值。后来发现这设计真香,错误处理直接 result, err := doSomething(),不用再搞什么异常捕获。 更绝的是 Go 的闭包、defer、方法绑定,这些特性组合起来,让代码既简洁又强大。 这篇就聊聊 Go 函数和方法的几个核心特性。 函数声明:类型在后面 Go 的函数声明用 func 关键字,参数类型写在变量名后面: 1func add(a int, b int) int { 2 return a + b 3} 4 5// 参数类型相同可以合并 6func add(a, b int) int { 7 return a + b 8} 这是 Go 的特色,跟 C/Java 反着来。习惯就好。 多返回值:Go 的杀手锏 Go 支持函数返回多个值,这在错误处理中特别好用: 1func divide(a, b int) (int, error) { 2 if b == 0 { 3 return 0, errors.New("division by zero") 4 } 5 return a / b, nil 6} 7 8// 调用时接收两个返回值 9result, err := divide(10, 0) 10if err != nil { 11 fmt.Println("Error:", err) 12 return 13} 14fmt.Println("Result:", result) 不需要某个返回值?用下划线 _ 忽略: 1result, _ := divide(10, 2) // 忽略错误(不推荐) 命名返回值:可以更简洁 给返回值命名后,可以直接在函数体内使用,还能用裸 return: ...

2024-07-17 · 7 min · 1314 words · 老墨

Data Race vs Race Condition

原文地址: https://blog.regehr.org/archives/490, 翻译并略作改动。 竞态条件(race Condition)是当事件的时间或顺序影响程序的正确性时发生的缺陷。一般来说,需要某种外部计时或排序非确定性来产生竞态条件;典型的例子有上下文切换、操作系统信号、多处理器上的内存操作和硬件 中断。 当程序中有两次内存访问时,就会发生数据竞争(Data Race): 目标为同一内存位置 由两个线程同时执行 不是读取操作 不是同步操作 上边这个定义来自微软研究院的 Sebastian Burckhardt。该定义的两个方面需要注意: “同时”意味着没有像锁这样的东西强制一个操作在另一个操作之前或之后发生。 “不是同步操作”是指程序可能包含特殊的内存操作,例如用于实现锁的操作,这些操作本身并不同步。 在实践中,它们两者存在相当大的重合:许多 Race Condition 是由 Data Race 引起的,并且许多 Data Race 导致 Race Condition。另一方面,两者也可以相互独立,可能产生没有 Data Race 的 Race Condition,也可能产生没有 Race Condition 的 Data Race。 让我们从一个在两个银行账户之间转移资金的简单函数开始: transfer1 (amount, account_from, account_to) { if (account_from.balance < amount) return NOPE; account_to.balance += amount; account_from.balance -= amount; return YEP; } 当然,这并不是银行真正转移资金的方式,但这个例子非常有用。我们知道,账户余额应该是非负的,并且转移之后不能凭空创造(多出)或损失(丢失)金钱。当在没有外部同步的情况下从多个线程调用时,该函数会产生 Data Race(多个线程可以同时尝试更新帐户余额)和 Race Condition(在并行上下文中它将创造或损失金钱)。 我们可以尝试这样修复它: transfer2 (amount, account_from, account_to) { atomic { bal = account_from.balance; } if (bal < amount) return NOPE; atomic { account_to.balance += amount; } atomic { account_from.balance -= amount; } return YEP; } 这里的“atomic”(原子性)是由语言运行时实现的,也许简单地通过在原子块开始时获取线程互斥体(Mutex)并在结束时释放它,也许使用某种事务(Transaction),或者也许通过禁用中断 —— 出于示例的目的,只要 atomic 块内的代码以原子方式执行就能解决竞争问题。 ...

2024-06-18 · 2 min · 235 words · 老墨

Golang中singleflight的实现原理

大家好,我是极客老墨。 上一篇我们学习了 singleflight 的用法,知道它可以抑制多个重复请求,极大地节约带宽、提升系统性能。用起来确实爽,但你有没有好奇过:这玩意儿底层到底是怎么实现的?为什么几行代码就能搞定并发控制? 今天我们就来扒一扒 singleflight 的源码,看看它的魔法到底藏在哪里。 核心思路:一个请求干活,其他请求白嫖 singleflight 的核心思路很简单:同一时间段内,对于相同的数据请求,只让第一个请求真正执行,其他请求全部阻塞等待。等第一个请求拿到结果后,直接把结果分享给所有等待的请求。 这就像食堂打饭,第一个人去窗口打饭,后面排队的人都等着。等第一个人打完,大家直接复制他的饭菜,不用再排队了。虽然这个比喻有点扯,但意思就是这么个意思。 回顾一下 singleflight 的公开 API: Group 对象:管理所有请求的大管家 Result 对象:执行结果的包装 Do 方法:同步执行,阻塞等待结果 DoChan 方法:异步执行,通过 channel 返回结果 从这些 API 可以推测:对于同一个 key,首个调用会执行真正的逻辑,后续相同 key 的调用都会阻塞,直到第一个请求返回。 singleflight 的源码不多,算上注释一共就 200 来行。我们来逐一分析。 Group:请求管理的大管家 先看 Group 的定义: 1type Group struct { 2 mu sync.Mutex // protects m 3 m map[string]*call // lazily initialized 4} Group 用一个 map[string]*call 存储所有正在执行的请求。为了保证并发安全,内部持有 sync.Mutex 锁来保护这个 map 的读写。 Group 有两个重要方法 Do 和 DoChan,在上一篇已经介绍过了。 再来回顾一下 Do 方法的定义: 1func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) 参数说明: key:标记同一请求的 key,相同 key 认为是相同请求 fn:真正执行业务逻辑的方法 返回值: ...

2024-06-10 · 4 min · 740 words · 老墨

使用 Let‘S Encrypt 为您的网站申请免费证书

网站的https证书过期了,一直使用阿里云的免费ssl证书,但是现在阿里云调整了策略,证书有效期从1年缩短到3个月了,所以我决定放弃阿里云转而使用 Let’s Encrypt 申请免费证书。 简介 Let’s Encrypt 是一家免费、开放、自动化的公益性证书颁发机构(CA), 由互联网安全研究组(ISRG)运作,详细介绍可以看这里。 申请证书 按照官方文档,对于没有命令行访问权限的网站,比如wordpress、cPanel等,可以通过控制台设置或者申请后手动上传证书;而我的网站服务器托管在阿里云,拥有命令行访问权限,所以直接使用官方推荐的 Certbot ACME 客户端来管理证书,这里详细记录下步骤。 CertBot 文档有详细的安装步骤,我的 ubuntu14 上的步骤如下: 安装 snapd snap 是linux的应用程序包,可以从其自身的snap store中安装、管理软件,有点类似包管理器如 apt、yum,但是不依赖linux发行版,安全、跨平台且无依赖;而 snapd 则是一个自动管理和维护 snap 的后台服务,它们之间的区别请参阅官方文档。 Ubuntu 16.04LTS版及之上的版本已经集成了snapd,无需再安装了,而我的是 14,所以需要手动安装。 sudo apt update sudo apt install snapd 测试是否安装成功,可以安装官方的 hello-world 程序: $ snap install hello-world 2024-06-09T16:15:39+08:00 INFO Waiting for automatic snapd restart... hello-world 6.4 from Canonical✓ installed 执行并成功输出信息表示安装成功: $ hello-world Hello World! 删除 certbot-auto 和任何 Certbot OS 软件包 如果您使用操作系统包管理器(如 apt 、 dnf 或 yum 安装了任何 Certbot 包,则应在安装 Certbot snap 之前删除它们,以确保运行命令 certbot 时,将使用 snap,而不是从操作系统包管理器进行安装。执行此操作的确切命令取决于您的操作系统,但常见的示例是 sudo apt-get remove certbot 、 sudo dnf remove certbot 或 sudo yum remove certbot 。 ...

2024-06-09 · 2 min · 374 words · 老墨

Golang中singleflight的用法

在开发过程中,尤其是web server,有时候我们需要并发的请求数据,对于某些数据,同时发起的多个请求其实拿到的数据都是相同的,如果不处理这类请求,那么每个请求都会获取一遍数据,这无疑是对资源的浪费。比如要从数据库查询相同 id 的一条数据,并发的多个请求都会执行一次 sql 语句,增加了数据库的压力。 有没有一种方案,当多个请求同时发起后,只有第一个请求去获取数据,在它返回之前,其他请求都各自阻塞等待直到真正执行数据获取的请求返回后,直接拿它的结果?我们把这种将同时发起的多个请求转为一个请求去执行真正业务逻辑的情况称为“请求抑制”。在 Go 中,singleflight 就是用来处理请求抑制的。 简介 singleflight 包全路径为 golang.org/x/sync/singleflight, 目前版本是 v0.7.0。 前边提到,singleflight 用于抑制同一时间获取相同数据的重复请求。当存在多个重复请求时,singleflight 保证只有一个请求能执行,其他请求阻塞,直到前边的请求返回后直接将其返回值共享(shared)给阻塞的这些请求。 在使用之前,首先要理解何为"重复请求",如何区分"相同数据"。 相同数据:指并发下当前时间段多个请求获取的数据是完全相同的,比如获取全局的配置、查询天气数据等。 重复请求:指处理相同数据时,在一个请求从发起到返回之前这段时间,又有其他多个请求发起,那么这些请求就是重复请求。 理解了这两点,现在我们来看看 singleflight 的用法。 用法 singleflight 整体设计比较简单,公开的 api包括: Group 对象: 它表示处理"相同数据"的一系列工作,在这里“重复请求”将会被抑制 Result 对象: 表示执行真正业务逻辑的结果对象 Do 方法: 执行请求抑制,后文详述 DoChan 方法: 与Do相同,只是结果返回 <-chan Result Do 方法 Do 方法表示执行请求抑制,其定义如下: 1func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) { 2 // ... 3} 首先,它需要在 singleflight.Group 实例下执行,用于抑制这一组请求。 两个参数: key: 这个参数用来指示"相同数据",也就是说相同的key代表相同数据,区分相同数据是正确使用 singleflight 的关键。 fn: 真正执行业务逻辑的方法,该方法返回一个任意对象 interface{} 和一个 error 返回结果: v: 就是 fn 方法返回的第一个值 err: fn 方法返回的第二值 error shared: 当抑制了其他请求,返回 true, 也就是说将真正执行业务逻辑的请求返回结果共享给其他请求后,该值为 true, 否则为 false DoChan 方法 DoChan 方法与 Do 类似,只是将结果封装为 Result 并返回它的 chan: ...

2024-05-18 · 3 min · 478 words · 老墨

Rust 学习笔记 03:函数

Rust 学习笔记 03:函数 “Talk is cheap. Show me the code.” – Linus Torvalds 在这一课,我们来聊聊编程语言中最基础的构建块——函数。 如果你熟悉 Go,你可能会觉得:“函数嘛,不就是 func 换成了 fn,大括号里写逻辑吗?” 确实,表面上看区别不大。但 Rust 引入了一个对 Go 开发者来说比较新颖的概念:语句 (Statements) 与 表达式 (Expressions) 的严格区分。这直接改变了代码的书写习惯(和逼格)。 1. 定义函数 Rust 使用 snake_case (蛇形命名法) 来命名函数,所有字母小写,单词间用下划线分开。 1fn main() { 2 println!("Hello, world!"); 3 another_function(); 4} 5 6fn another_function() { 7 println!("Another function."); 8} Go 也是这么写的(除了大括号位置 Go 强制不换行,Rust 推荐不换行但没强制)。 2. 参数与类型 和 Go 一样,Rust 是强类型语言,函数参数必须声明类型。这点没得商量。 1fn print_labeled_measurement(value: i32, unit_label: &str) { 2 println!("The measurement is: {}{}", value, unit_label); 3} 3. 语句 vs 表达式 (The Twist) 这是本节的重点! ...

2024-05-18 · 2 min · 275 words · 老墨