-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: add sync.Mutex.TryLock #27544
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
Comments
It seems this got cut off
Why not implement (or use an existing) third-party library that provides this, then you can see how many other gophers find it useful? That will provide a more compelling argument for adding it. |
If you use a 1-buffered channel as a mutex, |
That said, it would be helpful to see the broader context: the |
(In particular, it would be helpful to know what |
Fixed
No, because
I am communicating progress by sending it over a socket connection. It needs to be in a mutex, or else the progress bar gets pretty jittery from the delay of context-switches in between evaluating the current progress and sending it over the socket.
note that evaluating and sending progress is not super essential, but it does take a long time, and it needs to be done mutually exclusively (as illustrated above). It also should not be done in a raw mutex because it is non-essential - so I do not want goroutine 2 to need wait for goroutine 1. This would slow down all of my workers. I'd much rather just have the progress be sent again later. |
I tried to do this, it seems like |
I don't know your particular use case or your code, but what about something like this:
I do have a question, though: You mention that it gets 'jittery'. Do you mean that it jumps back and forth or just that it pauses and then jumps suddenly? What exactly are you trying to prevent from happening? |
Based on your description, I would expect to see something like: // Add adds n to the current progress.
func (b *ProgressBar) Add(n int) {
st := <-b.state
st.progress += n
for c, threshold := range st.waiting {
if st.progress > threshold {
c <- st.progress
delete(st.waiting, c)
}
}
b.state <- st
}
func (b *ProgressBar) Await(threshold int) (progress int) {
st := <-b.state
if st.progress > threshold {
b.state <- st
return st.progress
}
c := make(chan int, 1)
st.waiting[c] = threshold
b.state <- st
return <-c
} Then the “progress” goroutine (per @DeedleFake's suggestion) would look something like: progress := 0
for progress < b.Max {
progress = b.Await(progress)
updateClients(progress)
} |
I was actually thinking something more like this: func (b *ProgressBar) Add(n int) {
progress := atomic.AddInt64(&b.Progress, int64(n))
select {
case b.updateChan <- progress:
default:
}
}
func (b *ProgressBar) updater(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case progress := <-b.updateChan:
if progress >= b.Max {
b.once.Do(b.updateClientsDone)
return
}
b.updateClients(progress)
}
} You don't need to ever close or drain It sounds like |
Yeah this would actually work pretty well, I like this. (I personally don't like the Also - since there's no guarantee that the last progress update will be sent over the channel (because of the default case) it must be handled in the For other use cases, I guess you could just use a CAS on an integer to 1 for locked and 0 for unlocked. I'll close this issue since I think |
Glad I could help. The context is just being used for cancellation. If you don't want to use the whole |
Thank you all. This thread is a great example of helpful suggestions, keeping an open mind, and excellent discourse leading to a solution everyone seems happy with. |
This has been brought up before (#6123)
It seems the ultimate reason this was rejected was because there wasn't a good use case for it, which is what I have now, which is a progress bar for a parallel process.
Essentially, I was loading in several extremely large files, and I wanted a progress bar for how close I was to finishing. The progress bar could make very good use of TryLock.
Note that a server is loading in these files, and the client wants to view, in realtime, the progress of the changes.
Here is how I would like to implement a ProgressBar's Add function:
This is intended to work within a worker pool and be called (possibly) millions of times, so I really don't want to have it wait on any mutexes for long, or create any separate goroutines inside of it.
It also needs to be in some kind of mutex, as if it gets ran out of order, then the progress bar may begin to get jittery (as old values may get sent after new ones)
I also do not want to send the clients every update, but I do want to send updates as often as possible without significantly slowing down the program by waiting on the
b.pctMx
.I guess using a 3rd party library would work, but I'd really expect this to be first-class support, especially because this is a feature in most languages, and Go in particular has very good support for concurrency.
Essentially I guess what this proposal is trying to say, is that
TryLock
is the best way to run non-essential code inside a function that is called extremely often. If there is a better way to do this, please let me know.The text was updated successfully, but these errors were encountered: