Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: sync: add method Mutex.Locked(func()) #49563

Closed
twmb opened this issue Nov 12, 2021 · 8 comments
Closed

proposal: sync: add method Mutex.Locked(func()) #49563

twmb opened this issue Nov 12, 2021 · 8 comments

Comments

@twmb
Copy link
Contributor

twmb commented Nov 12, 2021

I'm raising this proposal to have the discussion once. There are alternatives that exist today, but I figure this may be worth considering. I could not find a similar proposal in the backlog of open nor closed issues (no results for scoped mutex, nor sync locked).

I propose to add sync.Mutex.Locked(func()):

func (m *Mutex) Locked(fn func()) {
        m.Lock()
        defer m.Unlock()
        fn()
}

Benefits

  • This can help eliminate a common problem of locking + deferring in loops.
  • This can guard against recovered panics leaving the mutex in a locked state if the user is not using defer to unlock.
  • Users are more incentivized to write functions without locks and rely on Locked to handle their synchronization, rather than embedding Lock / defer Unlock into the top of many functions.
  • It looks visually cleaner than the inline anonymous function with defers (deemed BenchmarkUgly in the play link below).
  • Users that have multiple unlock paths can be more incentivized to return from Locked and handle the result outside the lock/unlock paths.

Downsides

  • This adds one function as overhead on sync.Mutex, whereas the current implementation is a strictly to the point mutex.
  • Defer cannot be inlined at the moment (see cmd/compile: allow inlining of open-coded defer calls #38471), meaning the Locked function cannot be inlined, and neither can the function passed to it. Theoretically this slowness can be eliminated.
  • The alternatives that exist today are fine and have existed since Go 1.0.

Code example / benchmarks

https://play.golang.org/p/Tll9rFTuPUM

goos: linux
goarch: amd64
pkg: j
cpu: Intel(R) Core(TM) i7-10710U CPU @ 1.10GHz
BenchmarkNative-12    	95955990	        10.82 ns/op
BenchmarkLocked-12    	72775494	        14.65 ns/op
BenchmarkUgly-12      	86551378	        13.73 ns/op
@gopherbot gopherbot added this to the Proposal milestone Nov 12, 2021
@ianlancetaylor ianlancetaylor added this to Incoming in Proposals (old) Nov 12, 2021
@ianlancetaylor ianlancetaylor changed the title proposal: sync.Mutex: add Locked(func()) proposal: sync: add method Mutex.Locked(func()) Nov 12, 2021
@zephyrtronium
Copy link
Contributor

I think there are advantages to making this a function that takes a Locker rather than a method on Mutex. This would allow using it with either end of an RWMutex, or another custom Locker.

@esote
Copy link

esote commented Nov 13, 2021

database/sql has an internal function withLock() which also uses Locker instead of Mutex

// withLock runs while holding lk.
func withLock(lk sync.Locker, fn func()) {
	lk.Lock()
	defer lk.Unlock() // in case fn panics
	fn()
}

@andig
Copy link
Contributor

andig commented Nov 14, 2021

I'm not sure I would actually want to use the new API as it would increase indentation (at least for anonymous functions) and decrease readability. Additional counter arg is that one can already write this code as package level function today.

@rsc
Copy link
Contributor

rsc commented Dec 1, 2021

In general we try not to have two different ways to do something, and for better or worse we have the current idioms.

@rsc
Copy link
Contributor

rsc commented Dec 1, 2021

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc rsc moved this from Incoming to Active in Proposals (old) Dec 1, 2021
@bcmills
Copy link
Contributor

bcmills commented Dec 1, 2021

Now that we have generics on the way, I would rather see us move in a direction that also eliminates unlocked-access bugs, not just incrementally update Mutex for forgotten-defer bugs.

Perhaps a typed Mutex instead of a raw one (https://go.dev/play/p/djJQBj1hemU):

package sync

type Synchronized[T any] struct {
	mu Mutex
	val T
}

func (s *Synchronizedf[T]) Do(fn func(*T)) {
	m.mu.Lock()
	defer m.mu.Unlock()
	fn(&m.val)
}

(However, an API like the above probably ought to be prototyped and tested outside of the standard library to check its ergonomics.)

@rsc rsc moved this from Active to Likely Decline in Proposals (old) Dec 8, 2021
@rsc
Copy link
Contributor

rsc commented Dec 8, 2021

Based on the discussion above, this proposal seems like a likely decline.
— rsc for the proposal review group

@rsc rsc moved this from Likely Decline to Declined in Proposals (old) Dec 15, 2021
@rsc
Copy link
Contributor

rsc commented Dec 15, 2021

No change in consensus, so declined.
— rsc for the proposal review group

@rsc rsc closed this as completed Dec 15, 2021
@golang golang locked and limited conversation to collaborators Dec 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

No branches or pull requests

7 participants