controller中不可直接用go关键字启动goroutine,否则会丢失ctx、依赖注入及中间件状态,导致nil指针panic;必须提取参数传入独立任务函数,通过channel或队列解耦执行。

Controller里直接用go关键字会丢失ctx和依赖注入
在Iris MVC Controller方法中写go func() {}(),看似能异步执行,但实际会丢失当前请求的iris.Context、服务实例(如c.Service)、中间件状态,甚至引发panic——因为ctx不是线程安全的,且Iris的依赖注入作用域绑定在当前HTTP请求生命周期内。
常见错误现象:panic: runtime error: invalid memory address or nil pointer dereference,往往出现在异步goroutine里调用c.Service.DoSomething()时,c.Service为nil。
- 不要在Controller方法体里直接起goroutine处理业务逻辑
- 若必须异步,应把任务数据提取出来(如ID、参数),传入纯函数或独立任务函数
- 避免在goroutine里访问
ctx、c任何字段,包括c.TX、c.Logger等
用独立任务函数 + channel 或任务队列解耦
Iris本身不提供任务队列,但你可以用标准库chan或集成asynq/machinery等。关键是要把“触发”和“执行”分离:
- Controller只负责接收请求、校验参数、生成任务ID、写入数据库(标记为pending)、返回202 Accepted
- 后台worker进程(单独运行)监听队列,消费任务并执行真实逻辑(发邮件、生成报表、调第三方API)
- 任务结果通过回调URL、数据库状态更新或Redis Pub/Sub通知前端
示例(轻量级channel方案,仅限单机开发环境):
var taskCh = make(chan Task, 100)
<p>type Task struct {
UserID int64
Action string
}</p><p>func init() {
go func() {
for t := range taskCh {
// 真实耗时操作,不依赖ctx
sendWelcomeEmail(t.UserID)
}
}()
}</p><p>func (c *UserController) PostRegister() {
userID := c.Ctx.Params().GetInt64Default("id", 0)
if userID </p><h3>需要记录日志或DB事务时,别在goroutine里commit</h3><p>如果异步任务要写数据库,不能复用Controller里开启的事务(<code>c.TX</code>),因为事务绑定在当前请求上下文,goroutine启动时事务可能已提交或回滚。同样,<code>c.Logger</code>也不该跨goroutine使用。</p>
- 日志统一用全局logger(如
zerolog.Logger),带request ID字段即可追溯 - 数据库操作必须新建连接或从连接池获取,不要传
*sql.Tx进goroutine - 若需强一致性(如扣库存后发消息),改用本地事务+延时重试,而非异步
容易被忽略的一点:Iris的BeforeActivation注入的服务默认是单例,但它们内部状态(如缓存、连接)不一定线程安全。确认你用的service是否支持并发调用,否则需加锁或改用per-request实例。











