在go例程和PostgreSQL 9.5上,pg_locks
遇到了一些不一致的行为。
当我创建其他goroutines
并致电SELECT pg_try_advisory_lock(1);
时,结果不一致。
我可以尝试在某个时候获取锁,失败,再试一次并设法获取它,而同时没有人明确释放锁。
我创建了一个小程序来重现该问题。
在任何给定时刻只有1个goroutine将具有锁定。
设法获取锁定的goroutine的数量随时间增加。
package main
var dbMap *gorp.DbMap // init code omitted for brevity
func main() {
setup(10)
fmt.Println("after initialization,", countOwners(), "instances of lockOwners have the lock!")
for {
if _, err := dbMap.Exec("SELECT pg_sleep(1)"); err != nil {
panic(err)
}
fmt.Println(countOwners(), "instances of lockOwners have the lock!")
}
}
func countOwners() int {
possessLock := 0
for _, lo := range los {
if lo.hasLock {
possessLock++
}
}
return possessLock
}
var los []*lockOwner
func setup(instanceCount int) {
var wg sync.WaitGroup
for i := 0; i < instanceCount; i++ {
wg.Add(1)
newInstance := lockOwner{}
los = append(los, &newInstance)
go newInstance.begin(time.Second, &wg, i+1)
}
wg.Wait()
}
type lockOwner struct {
id int
ticker *time.Ticker
hasLock bool
}
func (lo *lockOwner) begin(interval time.Duration, wg *sync.WaitGroup, id int) {
lo.ticker = time.NewTicker(interval)
lo.id = id
go func() {
lo.tryToGetLock()
wg.Done()
for range lo.ticker.C {
lo.tryToGetLock()
}
}()
}
func (lo *lockOwner) tryToGetLock() {
if lo.hasLock {
return
}
locked, err := dbMap.SelectStr("SELECT pg_try_advisory_lock(4);")
if err != nil {
panic(err)
}
if locked == "true" {
fmt.Println(lo.id, "Did get lock!")
lo.hasLock = true
}
}
该程序的输出有所不同,但通常如下:
1 Did get lock!
after initialization, 1 instances of lockOwners have the lock!
1 instances of lockOwners have the lock!
2 Did get lock!
2 instances of lockOwners have the lock!
2 instances of lockOwners have the lock!
7 Did get lock!
3 instances of lockOwners have the lock!
3 instances of lockOwners have the lock!
6 Did get lock!
4 instances of lockOwners have the lock!
pg_locks
时应该受到什么保护?同一goroutine在下一次尝试中成功执行此操作的原因是什么?
goroutine
触发它都是从另一个线程执行的吗?这将解释行为不一致的情况。答案 0 :(得分:0)
在通过gorp使用PostgreSQL几个月后,我认为我理解了这种行为:
gorp
维护一个连接池。dbMap.SomeCommand()
创建一个事务,执行一些命令并提交该事务。pg_try_advisory_lock
适用于session level。无论何时执行dbMap.SelectStr("SELECT pg_try_advisory_lock(4);")
,都会从池中选择一个连接。然后创建一个事务,它在会话级别上获取锁 ,然后将其提交。
当另一个go-routine尝试执行相同的操作时,可能会使用相同会话,这可能取决于从池中获取的连接。由于锁定是在会话级别完成的,因此新事务能够再次获取锁定。