在并发Go应用中保持顺序【译】

原文链接 https://destel.dev/blog/preserving-order-in-concurrent-go 并发是 Go 语言的一大优势,但它带来一个根本性的权衡:当多个 goroutine 同时处理数据时,自然顺序会被打乱。大多数情况下,这并无大碍——无序处理已足够,且更快速、更简单。 但有时,顺序至关重要。 当顺序至关重要 以下是三个需要保持顺序至关重要的实际场景: 实时日志增强 :您正在处理高流量的日志流,通过数据库或外部 API 为每个条目添加用户元数据。顺序处理无法跟上输入速率,但并发处理会打乱顺序,使得增强后的日志对依赖时间顺序的下游消费者变得不可用。 在文件列表中查找首个匹配项 :您需要从云存储下载文件列表,并找到包含特定字符串的第一个文件。并发下载速度更快,但完成顺序是乱序的——第 50 个文件可能比第 5 个文件先完成,因此您不能简单地返回找到的第一个匹配项,因为无法确定更早的文件是否也包含该字符串。 时间序列数据处理 :这个场景激发了我的原始实现。我需要下载 90 天的交易日志(每个约 600MB),提取部分数据,然后比较连续日期的数据以进行趋势分析。顺序下载需要数小时;并发下载可实现数量级的速度提升,但会破坏我进行比较所需的时间关联性。 挑战很明确:我们需要在不牺牲结果顺序可预测性的前提下获得并发处理的速度优势。这不仅是理论问题——更是影响实际大规模系统的现实约束。 本文将探讨我在生产级 Go 应用中开发并采用的三种方法。我们将构建一个并发的 OrderedMap 函数,它能在保持顺序的同时将输入通道转换为输出通道,并支持具有背压机制的无限流处理。通过对每种方法进行基准测试,我们将理解其权衡取舍,并在此过程中发现令人惊讶的性能洞见。 问题:为何并发会破坏顺序 让我们快速回顾一下为什么并发会打乱顺序。原因之一是各个 goroutine 处理任务的速度不同。另一个常见原因——我们无法预测 Go 运行时如何精确调度 goroutine。 例如,goroutine #2 可能在 goroutine #1 完成第 10 项之前就处理完了第 50 项,导致结果顺序错乱。这是并发处理的自然行为。 若想查看实际效果,这里有一个在 Go Playground 上的快速演示 。 设计理念:背压与缓冲的权衡 传统的顺序并发方法采用某种重排序缓冲区或队列。当工作线程计算出结果但尚不能写入输出时,该结果会被暂存于缓冲区中,直至能够按正确顺序写入。 在这种设计中,缓冲区通常可以无限制地增长。这种情况发生在: 输入存在倾斜 – 早期项目的处理时间比后续项目更长 下游消费者处理速度较慢 另一种常见方法是将所有结果暂存于内存中(切片/映射等)再进行排序。但我们今天的目标是构建一个流式解决方案,它能够: 最小化延迟 – 结果一旦准备就绪立即输出 处理无限输入流 – 支持任意大甚至无限的输入(例如从标准输入或网络流读取) 保持内存受限 – 避免不必要地在内存中累积结果 话虽如此,下面介绍的算法是背压优先的。如果工作协程还无法将结果写入输出通道,它就会阻塞。这种设计受内存限制,并保持了开发者对 Go 通道行为的预期。...

Zsh自动完成文件的一个例子

亚马逊发布了kiro ,基于Claude,于是下载安装文件,发现在它的安装文件里面有那么一个文件。 文件内容如下 #compdef kiro local arguments arguments=( '(-d --diff)'{-d,--diff}'[compare two files with each other]:file to compare:_files:file to compare with:_files' \*'--folder-uri[open a window with given folder uri(s)]:folder uri: ' \*{-a,--add}'[add folder(s) to the last active window]:directory:_directories' '(-g --goto)'{-g,--goto}'[open a file at the path on the specified line and column position]:file\:line[\:column]:_files -r \:' '(-n --new-window -r --reuse-window)'{-n,--new-window}'[force to open a new window]' '(-n --new-window -r --reuse-window)'{-r,--reuse-window}'[force to open a file or folder in an already opened window]' '(-w --wait)'{-w,--wait}'[wait for the files to be closed before returning]' '--locale=[the locale to use (e....

使用go编写一个简单的人脸识别服务

需要下载相关的模型 https://github.com/Kagami/go-face-testdata 下面的models 代码实现 代码如下 package main import ( "encoding/base64" "encoding/json" "errors" "fmt" "io" "log" "math" "net/http" "os" "path/filepath" "strconv" "sync" "time" "unsafe" "github.com/Kagami/go-face" "github.com/gorilla/mux" ) // Config 配置结构 type Config struct { Port string `json:"port"` ModelsDir string `json:"models_dir"` UploadsDir string `json:"uploads_dir"` TempDir string `json:"temp_dir"` DataFile string `json:"data_file"` MaxFileSize int64 `json:"max_file_size"` DefaultThreshold float32 `json:"default_threshold"` LogLevel string `json:"log_level"` } // Person 人员结构(支持多样本) type Person struct { ID int `json:"id"` Name string `json:"name"` Samples []FaceSample `json:"samples"` Created time....

通过比较两个 Go pprof文件来发现性能问题【译】

原文链接 https://www.dolthub.com/blog/2025-06-20-go-pprof-diffing/ 我们正在努力进行兼容性工作 Doltgres,世界上首个且唯一的版本控制兼容 Postgres 的 SQL 数据库。这意味着它能够开箱即用地与 Postgres 兼容的每一款库和工具协同工作。近来我们投入了大量精力在 SQLAlchemy 上,这是一款流行的 Python ORM。他们的 MySQL 集成与 Dolt 配合完美无缺,但他们的 Postgres 版本显然完全不同,严重依赖 pg_catalog 表。一位客户尝试使用后发现存在许多空白 ,因为 Doltgres 未将系统表(例如 dolt_log)包含在 pg_catalog 表中。所以我修复了这个问题,但这导致我们其中一个测试套件出现了神秘的性能退化,速度慢了 3 倍。 费了相当大的心思才弄清楚为什么这样一个看似无害的更改会导致性能出现如此巨大的差异。最终,最有帮助的是一个令人惊叹的 Go 工具链工具:使用 -base 选项来可视化两个性能分析之间的差异。 pprof 使用 pprof 对两个 profile 进行差异比较 Go 自带了一个强大的性能分析工具,pprof。与某些其他语言不同,您必须在代码中显式启用它才能获取性能分析结果;您不能事后进行或使用命令行标志。这很简单,但您必须编写代码来实现它。在我们的案例中,我将它直接放置在被分析的性能测试方法中。 func TestRegressionTests(t *testing.T) { // We'll only run this on GitHub Actions, so set this environment variable to run locally if _, ok := os.LookupEnv("REGRESSION_TESTING"); !ok { // t....

Git Submodule Update 命令备忘笔记

核心命令差异分析 git submodule update 行为:将子模块检出到父仓库记录的特定提交(commit hash) 特点:保持与父仓库 .gitmodules 和索引中记录的版本一致 适用场景:需要精确重现项目某个时间点的状态 局限性:不会拉取远程最新提交,只同步到父仓库索引中记录的版本 git submodule update --remote --recursive 行为:将子模块更新到远程分支的最新提交 特点:忽略父仓库记录的提交,直接拉取远程最新版本 适用场景:需要获取子模块的最新开发进度 常见困惑:为什么普通 update 拉取不到最新版本? 问题现象 许多开发者会遇到这种情况: # 执行普通更新,发现子模块没有更新到最新版本 git submodule update # 但是使用 --remote 参数却能拉取到最新版本 git submodule update --remote --recursive 根本原因分析 这个现象的核心在于 Git 子模块的版本绑定机制: 父仓库记录的是具体提交:当你添加子模块时,父仓库会在其索引中记录子模块的具体 commit hash 普通 update 遵循绑定版本:git submodule update 只会将子模块检出到父仓库记录的那个具体提交 –remote 忽略绑定版本:git submodule update --remote 会直接从远程仓库拉取最新提交 实际演示场景 # 查看当前子模块状态 git submodule status # 输出示例:-a1b2c3d4 path/to/submodule (v1.0-5-ga1b2c3d) # 父仓库记录的是 a1b2c3d4 这个提交 # 即使远程仓库已经有新的提交 e5f6g7h8 # 使用普通 update git submodule update # 子模块仍然停留在 a1b2c3d4 # 使用 --remote 参数 git submodule update --remote # 子模块更新到最新的 e5f6g7h8 详细参数说明 --remote 参数 # 更新到远程分支最新版本 git submodule update --remote # 指定远程分支 git submodule update --remote --branch main --recursive 参数 # 递归更新嵌套的子模块 git submodule update --recursive # 组合使用 git submodule update --remote --recursive 其他常用参数 # 强制更新(丢弃本地修改) git submodule update --force # 初始化并更新 git submodule update --init --recursive # 并行更新(提高速度) git submodule update --jobs 4 配置选项管理 局部配置(仅当前仓库) 设置子模块默认更新行为 # 设置特定子模块跟踪远程分支 git config -f ....

Claude Code使用笔记

来自https://x.com/shao__meng/status/1950196917595754662的ClaudeCode技巧 安装 你需要有nodejs这样的环境,bun没进行测试.需要安装npm或者pnpm包管理器 安装 Claude Code nodejs环境 pnpm install -g @anthropic-ai/claude-code bun环境 Option 1: Install globally and run bun add -g @anthropic-ai/claude-code bun run --bun claude Option 2: Use bunx to run directly bunx --bun @anthropic-ai/claude-code Add MCP Server (This works!) You can add the MCP server using this command: claude mcp add --transport http context7 https://mcp.context7.com/mcp --header "CONTEXT7_API_KEY: $CONTEXT_API_KEY" deno环境 安装Deno curl -fsSL https://deno.land/install.sh | sh 全局按照Claude code...

Go语言中的指数加权移动平均

Tailscale中有很多实用的代码,下面是EWMA的一个实现,源码 // Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause // Package maths contains additional mathematical functions or structures not // found in the standard library. package maths import ( "math" "time" ) // EWMA is an exponentially weighted moving average supporting updates at // irregular intervals with at most nanosecond resolution. // The zero value will compute a half-life of 1 second. // It is not safe for concurrent use....

在go中使用Semaphoregroup

在netbird中看到一个semaphore-group函数 package semaphoregroup import ( "context" "sync" ) // SemaphoreGroup is a custom type that combines sync.WaitGroup and a semaphore. type SemaphoreGroup struct { waitGroup sync.WaitGroup semaphore chan struct{} } // NewSemaphoreGroup creates a new SemaphoreGroup with the specified semaphore limit. func NewSemaphoreGroup(limit int) *SemaphoreGroup { return &SemaphoreGroup{ semaphore: make(chan struct{}, limit), } } // Add increments the internal WaitGroup counter and acquires a semaphore slot. func (sg *SemaphoreGroup) Add(ctx context....

在 Go 中构建可扩展的多租户应用程序【译】

原文链接 https://atlasgo.io/blog/2025/05/26/gophercon-scalable-multi-tenant-apps-in-go 为 GopherCon Israel 2025 准备并呈现。 引言 在本篇博客中,我们将基于我们在构建 Atlas Cloud 后端(作为我们商业产品的一部分)的经验,探讨在 Go 中构建可扩展多租户应用程序的不同策略。 但首先,让我们明确一下我们所说的多租户应用是什么。 多租户是一个系统的特性,即单个实例为多个客户(租户)提供服务。 作为一家商业企业,你的目标当然是有很多客户!但你想要服务许多客户,他们期望有一个流畅无缝的体验,就好像只有他们在使用你的服务一样。 你向客户隐含做出的两个重要承诺是: 数据隔离:每个租户的数据都是隔离和安全的,确保一个租户无法访问另一个租户的数据。 性能:无论租户数量如何,应用程序都应表现良好,确保一个租户的使用不会降低其他租户的体验。 让我们探讨一些可能实现这些承诺的方法。 物理隔离 确保数据和性能隔离最直接的方法是为每个租户运行一个独立的应用实例。这种方法通常被称为“物理隔离”或“专用实例”。 为每个租户运行独立实例,可以确保: 每个租户的数据存储在独立的数据库中,从而保证完全隔离。如有需要,租户可以在不同的 VPC 中运行,甚至可以在不同的云账户中运行。 租户独立消费资源,因此一个租户的使用不会影响其他租户,从而消除了“吵闹邻居”问题。 然而,大多数公司不会选择这条路,原因有几点: 运营开销:将应用程序部署到数百或数千个生产环境,每个环境都有自己的数据库和配置,管理起来可能非常复杂。 成本:如果您的公司需要为每个租户的资源支付云服务提供商的费用,成本可能会迅速变得难以承受。 可扩展性:如果添加新租户需要部署新实例,那么扩展应用程序以支持许多租户可能会成为瓶颈。 可见性:跨多个实例监控和调试问题可能具有挑战性,因为您需要从所有实例中聚合日志和指标。 逻辑隔离 另一种方法是运行单个应用程序实例来服务多个租户,通常称为“逻辑隔离”。在此模型中,租户共享相同的应用程序代码和数据库,但它们的数据在逻辑上是隔离的。逻辑隔离可以总结为: 共享基础设施,作用域请求 让我们看看在 Go 应用程序中实际如何使用这个示例,从一个简单的 GORM 示例开始: package main type Tenant struct { ID uint `gorm:"primaryKey" json:"id"` Name string `json:"name"` } type Customer struct { ID uint `gorm:"primaryKey" json:"id"` Name string `json:"name"` TenantID uint `json:"tenant_id"` } 在这个示例中,我们有两个模型: Tenant 和 Customer 。每个 Customer 属于一个 Tenant , Customer 模型中的 TenantID 字段用于将每个客户与特定的租户关联起来。...

TIL:Bash 脚本中的超时【译】

原文链接 https://heitorpb.github.io/bla/timeout/ 前几天在工作中,我们有一个 Bash 脚本,用于设置一个 Web 服务器,并等待它启动后再继续执行后续操作。这个脚本运行正常,我们也没有遇到任何问题,直到出现了一个无限循环。 我们使用 Bash 内置的 until 来检查 Web 服务器是否正常: until curl --silent --fail-with-body 10.0.0.1:8080/health; do sleep 1 done 这很好用。除非我们的 Web 服务器在启动过程中崩溃,并且我们 sleep 1 永远等待。 这里有一个实用的工具:timeout。顾名思义,这个命令可以为其他命令添加超时功能。您指定想要等待命令的时间限制,如果该时间已过,timeout 会发送一个信号来终止它,并以非零状态退出。默认情况下,timeout 发送的是 SIGTERM 信号,但您可以通过 --signal 标志来更改它,例如 timeout --signal=SIGKILL 1s foo 。 例如,timeout 1s sleep 5 将向 sleep 发送 SIGTERM 信号 1秒后: $ time timeout 1s sleep 4 real 0m1,004s user 0m0,000s sys 0m0,005s $ echo $? 124 那么接下来应该将 timeout 和 until 结合起来:...