Iris MVC Controller 默认单例,共享字段非并发安全;应避免在Controller中定义可变状态,改用sync.Map、sync.Once或请求级依赖注入;Service层需自行保证线程安全;所有I/O操作须带超时控制。

Controller 方法本身是并发安全的,但共享字段不是
Iris 的 MVC Controller 实例默认是单例(Singleton)模式,即整个应用生命周期内只创建一次。这意味着所有请求共用同一个 struct 实例,如果在 Controller 中定义了可变字段(比如 count int 或 cache map[string]string),多个并发请求同时读写它就会出现数据竞争。
常见错误现象:返回结果错乱、map panic(fatal error: concurrent map writes)、计数器值不符合预期。
- 避免在 Controller 结构体中声明可变状态字段;如需缓存或计数,改用
sync.Map或sync.Once+ 全局变量 - 若必须按请求隔离状态,改用依赖注入方式传入 request-scoped 对象(例如通过
BeforeActivation注册每次请求新建的 service) - 不要在
Get()、Post()等方法里直接修改 Controller 的字段,除非加锁(不推荐,影响性能)
Service 层要自己保证并发安全
Iris 不干涉你写的 Service、Repository 或 Client,它们是否线程安全完全由你控制。比如数据库连接池(*sql.DB)本身是并发安全的,但你自己封装的内存 cache 若用原生 map 就不是。
使用场景:用户登录态校验、订单号生成、本地限流计数器。
- 优先复用标准库并发安全类型:
sync.Map、sync.Pool、atomic.Int64 - gRPC 客户端(
pb.XxxClient)是并发安全的,可全局单例复用,无需每个请求重建连接 - Redis / MySQL 客户端只要底层驱动支持连接池(如
go-redis、gorm),也默认支持高并发,但注意连接池大小配置(如SetMaxOpenConns)
别在 Controller 里做耗时同步操作
Iris 的 HTTP handler 是基于 goroutine 调度的,每个请求天然并发执行。但如果某个 GetByID() 方法里同步调用一个 2 秒的外部 HTTP 接口,那它会阻塞当前 goroutine,虽然不影响其他请求,但会快速吃光可用 goroutine(尤其在高 QPS 下)。
容易踩的坑:用 time.Sleep 模拟延迟、未设超时的 http.DefaultClient.Do()、阻塞 channel 等待。
- 所有 I/O 操作必须带上下文超时:
ctx.Timeout(3 * time.Second),并传给下游 client - 避免在 Controller 方法里启动无管控的 goroutine(如
go sendEmail()),应交由任务队列(如 Asynq、Beanstalkd)处理 - 若必须异步触发,至少用带缓冲的 channel 或 worker pool 控制并发量,防止 goroutine 泛滥
中间件和 BeforeActivation 不影响并发模型
BeforeActivation 只在应用启动时执行一次,用于注册路由、绑定依赖,它不参与请求处理流程;而中间件(app.Use())每次请求都会执行,但它本身也是并发安全的——Iris 为每个请求新建独立的 iris.Context,中间件函数内对 ctx 的操作不会跨请求污染。
真正要注意的是你在中间件里挂载的共享资源,比如:
- 如果你在中间件里往
ctx.Values().Set("user", u)存东西,没问题——这是请求级的 - 但如果你在中间件里修改了一个全局
var stats = struct{ mu sync.RWMutex; total int }{},就必须加锁 - 日志中间件(
logger.New())内部已用 sync.Pool 复用 buffer,不用额外处理
复杂点在于:Controller 单例 + Service 共享 + Context 隔离,这三层边界稍不注意就会把 request-local 状态误当成 global state。盯住变量声明位置和初始化时机,比加锁更有效。


















