前言
在Golang中,并发编程变得容易实现,仅需要起一个goroutine即可开启并发模式:
go f("goroutine")
但是,在并发编程中存在不少需要注意的地方。这篇文章简单讨论下,在多个goroutine对MongoDB数据库进行操作时,保证操作一致性的方法。
问题描述
假设我们起了多个goroutine,如下:
go func() {
//step1: 获取所有 status==true 的记录
records, _ := models.Record.FindStatus(true)
for _, v := records{
//step2: then we do something about record
v.DoSomething()
//step3: 将这些记录的 status 更新为 false
v.status = false
if err := v.Save(); err != nil{
return
}
}
}()
以上代码从数据库中取得所有 `status == true` 的记录,随后对这些记录进行操作,最终将记录的状态设置为 `false`。
仔细一看,这段代码明显存在问题,描述如下:
- 假设有两个goroutine: goroutineA, goroutineB 都进行了以上操作
- goroutineA 执行了step1 取出状态为 `true` 的记录,并且进行了 step2 操作
- 当 goroutineA 执行到 step2 但是 step3 还未执行完成时,goroutineB 也有可能成功执行 step1。随后goroutineB 也执行了 step2。显然,这不是我们所预期的结果。
解决方案
解决问题的主要思路是:
- 为 status 多设置一个中间状态 pending,代表该记录正在被处理中
- 添加SetStatus方法,在Update的时候确保 `status`字段未被其他事务改动
参考代码如下:
type RecordStatus int
const(
StatusBefore RecordStatus = iota
StatusPending
StatusAfter
)
func (record *RecordModel) SetStatus(status RecordStatus) (err error){
Record.Query(func(c *mgo.Collection) {
query := bson.M{
"_id": Domain Premium Untuk Bisnis Anda,
"status": record.Status,
}
update := bson.M{
"status": status,
"updated_at": time.Now(),
}
err = c.Update(query, bson.M{
"$set": update,
})
if err == nil {
record.Status = status
}
})
return
}
go func() {
//step1: 获取所有 status==true 的记录
records, _ := models.Record.FindStatus(true)
for _, v := records{
// step2: set record status to be "pending"
if err := v.SetStatus(StatusPending); err != nil{
return
}
//step3: then we do something about record
v.DoSomething()
//step4: 将这些记录的 status 更新为 false
if err := v.SetStatue(StatusAfter); err != nil{
return
}
}
}()
通过以上方法可以确保不会同时有多个goroutine 对一条数据进行了更新操作。
我的微信公众号: EasyHacking
Go设计模式陆续更新中 github: csxuejin/go-design-patterns
有疑问加站长微信联系(非本文作者)