在 GORM 中,每个链式方法都会调用 clone
函数来创建并克隆一个新的 DB 对象。这个函数的实现如下:
func (s *DB) clone() (db *DB) { db = &DB{ Error: s.Error, RowsAffected: s.RowsAffected, Statement: &Statement{}, Callbacks: s.Callbacks, Config: s.Config, Logger: s.Logger, } db.Statement.DB = db db.Statement.cloneFrom(s.Statement) return }
这个函数会创建一个新的 DB 对象,并复制原来 DB 对象的一些属性,比如 Error
、RowsAffected
、Callbacks
、Config
和 Logger
。然后它会调用 cloneFrom
函数来复制原来 DB 对象的 Statement
。
这样,每次调用链式方法时,都会创建一个新的 DB 对象,而不是修改原来的对象。这样可以保证在多 goroutine 的情况下,每个 goroutine 都有自己独立的 DB 对象,从而避免了数据竞争和并发问题。
Statement对象
在 GORM 中,Statement
是一个结构体,它用于存储和管理与数据库操作相关的信息。它的定义如下:
type Statement struct { *DB TableExpr clause.Expr Model interface{} ReflectValue reflect.Value Schema *schema.Schema Clauses map[string]clause.Clause NamedExprs []clause.NamedExpr SQL strings.Builder Vars []interface{} Dest interface{} }
Statement
结构体包含了许多字段,比如 TableExpr
、Model
、ReflectValue
、Schema
、Clauses
等。这些字段用于存储和管理与数据库操作相关的信息。
例如,当你调用 Where
方法时,GORM 会在 Statement.Clauses["WHERE"]
中存储 WHERE 子句的信息。当你调用 Find(&users)
方法时,GORM 会在 Statement.Dest
中存储查询结果。
多 goroutine 中如何保证并发安全
在 GORM 中,每个链式方法都会创建并克隆一个新的 DB 对象。这意味着每次调用链式方法时,都会创建一个新的 DB 对象,而不是修改原来的对象。这样可以保证在多 goroutine 的情况下,每个 goroutine 都有自己独立的 DB 对象,从而避免了数据竞争和并发问题。
例如,在下面的代码中:
var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() db.Where("id = ?", i).First(&User{}) }(i) } wg.Wait()
每个 goroutine 都会调用 Where
方法来创建一个新的 DB 对象。这样,每个 goroutine 都有自己独立的 DB 对象,从而避免了数据竞争和并发问题。
总之,在 GORM 中,所有链式方法都会创建并克隆一个新的 DB 对象 (共享一个连接池),以保证在多 goroutine 的情况下并发安全。
sync.WaitGroup 的用法 代码示范
sync.WaitGroup
是 Go 语言中的一个结构体,它用于等待一组 goroutine 完成。它的常用方法有 Add
、Done
和 Wait
。
下面是一个简单的使用 sync.WaitGroup
的例子:
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { defer wg.Done() fmt.Println(i) }(i) } wg.Wait() }
在这个例子中,我们创建了一个 sync.WaitGroup
对象,并在循环中启动了 5 个 goroutine。每次启动一个 goroutine 时,我们都会调用 wg.Add(1)
来增加计数器。
在每个 goroutine 中,我们都会调用 defer wg.Done()
来减少计数器。当计数器减为 0 时,wg.Wait()
方法就会返回。
这里为什么要 defer 不使用的影响
在上面的例子中,我们使用了 defer wg.Done()
来确保在 goroutine 结束时调用 wg.Done()
方法。defer
关键字用于延迟执行一个函数,直到包含它的函数返回。
如果我们不使用 defer
,而是直接调用 wg.Done()
,那么就有可能在 goroutine 还没有完成时就调用了 wg.Done()
方法。这样会导致计数器减少得过快,从而使得 wg.Wait()
方法过早地返回。
例如,在下面的代码中:
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(i int) { fmt.Println(i) wg.Done() }(i) } wg.Wait() }
我们没有使用 defer
来延迟执行 wg.Done()
方法。这样,在某些情况下,可能会出现计数器减少得过快,从而使得 wg.Wait()
方法过早地返回。