-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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, sync/atomic: add PoolOf, MapOf, ValueOf #47657
Comments
Would it be possible to remove the double implementation that would result from this using type aliases? For example, type Pool = PoolOf[interface{}] I'm not familiar enough with the nuances of type aliases to be sure if this is fully backwards compatible or if it would have weird edge cases, such as in embedding. (I'm not actually entirely sure that it's legal to alias to a generic instantiation.) |
@DeedleFake Unfortunately it's not quite the same. The current version of |
That said, it's likely that |
The The second return in the generic implementation doesn't seem to map to any existing behavior. Edit: It can return |
For |
So, just to clarify, what you're saying is that the incompatibility isn't non-obvious
but the current implementation's return of |
This is exactly the circumstance under which the current Get returns nil, assuming New never returns nil itself. |
No it isn't. The current implementation returns It can't possibly be the same functionality as is proposed here because that would mean that it was both returning Edit: You could use it simulate the proposed functionality by manually setting Edit 2: The documentation does say that Edit 3: Alright, so I did a quick survey and it seems like there are some cases of people using this functionality of Either way, I feel like this is getting further and further off of the main topic. I'm fully in favor of this proposal, regardless of these minor details. |
It's true that if the pool's |
Honestly, the more I think about it the more that what I find strange about the existing API isn't the behavior when As long as new implementations are being built anyways, could type Pool struct {
p PoolOf[interface{}]
New func() interface{}
}
func (p *Pool) Get() interface{} {
v, ok := p.p.Get()
if !ok && (p.New != nil) {
return p.New()
}
return v
}
// And Put(), too. |
Documentation is going to be very messy with similarly named structures/functions, as will autocomplete. As #47619 (comment) says, maybe it's time for |
I've been wondering recently if maybe now that modules are a well-accepted thing, everything that isn't key to the functioning of the language should be moved into Edit: I forgot to mention that a potential downside to this is complicating bootstrapping a Go compiler onto a system with limited or no network capabilities, as basic functionality could wind up being external accidentally, but I'm not sure that this is nearly a big enough problem to really worry about. |
This proposal has been added to the active column of the proposals project |
// Note: no CompareAndSwap method; it would require a comparable T. Not having CompareAndSwap is quite unfortunate. I imagine something like the following could fill the gap, but it would be ugly-ish and it's probably why it has been left out: func CompareAndSwap[T comparable](v *ValueOf[T], old, new T) (T, bool) (update: or, obviously, something like) type ComparableValueOf[T comparable] struct{
v ValueOf[T]
}
func (v *ComparableValueOf[T]) CompareAndSwap(old, new T) (T, bool) Are we omitting it now because we think there are going to be better ways to do this more cleanly in the future? |
One possibility is to permit generic types to have methods that use different, tighter, constraints. Those methods would only be available if the type argument(s) satisfied those constraints. I don't know whether that would be a good idea or not. |
As a counterpoint, I've never not used
vs.
and, some of these instances are In short, |
It's maybe worth pointing out that
|
One way to avoid the boolean return of This assumes that there are no cases where you'd want to use |
It depends on how it is implemented. I am not sure that it would necessarily be implemented in a way that allows changing the underlying interface type. |
I am increasingly uncomfortable with the long-term effects of the "Of" suffix. |
A thought regarding To me, that merits splitting I'm not sure whether that makes the naming problem easier or harder. 😅 |
That's an interesting thought. If we had a constructor, say |
Technically you might only need one type AFAICS, as type constraints are expressive enough to allow most of the types that can be accessed atomically (notably absent are struct types containing single pointerlike values).
You'd want to be able to do a generic type-parameter switch though, and generic type aliases would be nice too:
The code might look like this: https://go2goplay.golang.org/p/LVak2XRNC81 |
I see that discussion is labeled as closed. Is it being actively discussed somewhere? |
I don't know. It's early days yet. |
About the
IMHO:
Can be used like:
|
A performance advantage of a generic In https://go.dev/cl/471200, I struggled with this problem and decided to pool |
I dealt with the pooling |
I agree with @muhlemmer that the My argument against
Yes, dropping |
Given that #56102 was approved, can this come off of hold and be evaluated? |
Hi @carlmjohnson, there is a decent chance resolution here is at least somewhat intertwined with #48287 and how to approach this category of change? |
Yes, this should now be considered to be on hold for further thoughts along the lines of #48287. We don't have a general plan for updating packages to add generic support. |
I'm proposing to use the opportunity to extend sync.Pool to also allow Put() and Get() of multiple objects in a single call. This proposal could be implemented by adding a few new methods: // PutFromSlice adds all entries from xs to the pool.
//
// It behaves like the following code:
// for _, x := range xs {
// pool.Put(x)
// }
func (p *PoolOf[T]) PutFromSlice(xs []T)
// FillSlice selects arbitrary items from the Pool, removes them from the Pool,
// and places them into xs.
//
// If there is not enough items to fill the slice, the remaining part of the
// slice is filled with nils. But if p.New is non-nil, the remaining part is
// filled with the results of calling p.New repeatedly.
//
// It behaves like the following code:
// for i := range xs {
// xs[i] = pool.Get()
// }
func (p *PoolOf[T]) FillSlice(xs []T) The motivation is performance in the context of packet processing. It's starting to become a pattern in my hot path to have a slice of objects to Get() or Put(). Right now I'm just calling Get/Put in a loop to fill/release such slices. One unfortunately common detail that I have going on is that one goroutine does the Gets and after some processing it's some another goroutine that Puts those objects back to the pool. When under load this ends up meaning that the goroutine doing the Gets mostly ends up taking the slow path of Get(). Get() of a slice of objects surely allows new optimizations in the slow path considering how complex of a data structure Pool is. Even under light load the common case for me is a Put/Get of len(slice)>=4 and a small hiccup can cause len(slice)>=16. Under heavy load len(slice)>=42 and in the worst case len(slice)>=128. As I explained above under heavy load the Get() path consistently takes the slow path so I would really like to see a batch API of some sort included. |
Good idea. This can show clear requirements,not interface that represents any. Type assertions are no longer needed for performance |
@ianlancetaylor #48287 seem closed now? I don't understand the "discussion" system so I don't know if there was an actual output from that |
math/rand is becoming math/rand/v2 in Go 1.22, so maybe the answer is to add sync/v2 for a generic map. |
At the moment, I agree: our path forward will be a sync/v2 package. We're going to see how math/rand/v2 plays out first. |
Having just implemented a concurrent map for #62483, I'd like to argue for retaining the Perhaps the new |
I'll note that if we adopted #65394, you could have the constraints on |
This proposal is for use with #43651. We propose using type parameters to add compile-time-type-safe variants of
sync.Pool
,sync.Map
, andatomic.Value
.This proposal is not targeted for any specific Go release.
The text was updated successfully, but these errors were encountered: