对goroutine的“选择性”互斥

I am new to Go and I would like to implement a custom mutual exclusion mechanism, where each application user can execute one goroutine at a time. To simplify, consider U1 and U2 as application users and F1(userId), F2(userId) and F3(userId) three different goroutines, which read/write records from a database that are related only to a given user. I would like that, if U1 calls (F1, F2, F3), (F2, F3) are not executed until F1 has ended, than F2 or F3 are executed (in calling order would be an optimal solution), lastly the remaining one is executed. U2 is not affected by U1 lock, but she is controlled by its own lock as described before. How would you implement this? Is there a built-in primitive? Thanks

You can use a buffered channel as a semaphore, and create such a channel per user. Then two goroutines related to the same user will only be able to do things after acquiring the semaphore (successfully writing to the channel).


Resources on using Go channels as semaphores:

Use sync.Mutex for mutual exclusion. Use one mutex for each active user.

I'll assume that users are identified by some sort of id. Let's call that type userID. Use a map keyed by userID to store the per user mutex.

A mutex is only needed for active users, not all potential users. Use an integer counter to determine if a user is active.

The aforementioned map and counters should also be protected by a mutex.

Here's the code:

type userID string // replace userID type with id type for your application

// userLock is the per user lock
type userLock struct {
    sync.Mutex

    // n is number of goroutines between lock and unlock
    n int
}

var (
    // mu protects locks map and n field in userLock
    mu    sync.Mutex

    // locks is map of active locks
    locks = map[userID]*userLock{}
)


// lockUser locks the user specified by id. If the lock is
// already in use, the calling goroutine blocks until
// the lock is available.
func lockUser(id userID) {
    mu.Lock()
    l := locks[id]
    if l == nil {
        l = &userLock{}
        locks[id] = l
    }
    l.n++
    mu.Unlock()
    l.Lock()
}

// unlockUser unlocks the user specified by id. It 
// is a run-time error if the user is not locked on
// entry unlockUser.
func unlockUser(id userID) {
    mu.Lock()
    l := locks[id]
    if l == nil {
        panic("unlock without lock")
    }
    l.n--
    if l.n == 0 {
        delete(locks, id)
    }
    mu.Unlock()
    l.Unlock()
}