Postgres锁在不同的goroutine上的行为不一致

时间:2018-09-04 14:14:12

标签: multithreading go goroutine postgresql-9.5 locks

在go例程和PostgreSQL 9.5上,pg_locks遇到了一些不一致的行为。

当我创建其他goroutines并致电SELECT pg_try_advisory_lock(1);时,结果不一致。

我可以尝试在某个时候获取锁,失败,再试一次并设法获取它,而同时没有人明确释放锁。

我创建了一个小程序来重现该问题。

程序流程

  1. 创建10个goroutine。他们每个人都试图在初始化时获得相同的锁定。
  2. 每秒钟,每个实例将尝试再次获取该锁(如果尚未获取的话)。
  3. 我每秒检查一次所有实例,并计算已经获得锁的数量。

预期的行为:

在任何给定时刻只有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!

我的问题:

  1. 以这种方式使用pg_locks时应该受到什么保护?
  2. 某些goroutine无法获取锁的原因是什么?
  3. 同一goroutine在下一次尝试中成功执行此操作的原因是什么?

    • 难道是线程被资源锁定了吗?每次goroutine触发它都是从另一个线程执行的吗?这将解释行为不一致的情况。

1 个答案:

答案 0 :(得分:0)

在通过gorp使用PostgreSQL几个月后,我认为我理解了这种行为:

  • gorp维护一个连接池。
  • 无论何时创建交易,这些连接之一都会被随机选择(?)。
  • dbMap.SomeCommand()创建一个事务,执行一些命令并提交该事务。
  • pg_try_advisory_lock适用于session level
  • A session is synonymous with a TCP connection,所以它绝不是稳定的或永久的。
  • 池中的连接在可能的情况下仍使用相同的会话,但在需要时将其重置。

无论何时执行dbMap.SelectStr("SELECT pg_try_advisory_lock(4);"),都会从池中选择一个连接。然后创建一个事务,它在会话级别上获取锁 ,然后将其提交。

当另一个go-routine尝试执行相同的操作时,可能会使用相同会话,这可能取决于从池中获取的连接。由于锁定是在会话级别完成的,因此新事务能够再次获取锁定。