On 31 May 2012 15:29, Ugorji Nwoke <ugo...@gmail.com> wrote:
>> I'm hoping to avoid creating that closure and calling once.Do for every
>> request (by using that inited variable), but it's not clear to me if one
>> goroutine may see inited = true while still not seeing the write to initErr
>> from the doInit() call.
The point of using sync.Once is that it will run the function passed
to the Do method only once, independently of how many times you call
the method. So, you don't need the inited variable at all. After the
method returns, you are sure initErr will be set appropriately, as
long as you know the closure passed to Do is the only place where it
is modified. You may need to use a Mutex or channels to be sure of
this, but it probably is not necessary.
--
- yiyus || JGL .
The definitive answer is right there in the document you linked to. What you are trying to do is the same as the second example listed under "Incorrect synchronization":http://golang.org/ref/mem#tmp_123
Some goroutines may see uninitialized values.Peter
On May 31, 4:06 pm, Ugorji Nwoke <ugo...@gmail.com> wrote:
> My question was really:
> - In the case of a struct, is it possible for one goroutine to see the
> write to a member (initErr) without seeing the write to another member
> (inited), where both writes were done within a sync package call (once.Do
> here which uses mutexes internally).
To my (limited) understanding: Yes, this can happen. The
once.Do prevents the multiple execution of its argument (e.g.
by different goroutines) but it does not synchronize read/write
access to your struct, just because this struct contains a
sync.Once.
If you are are afraid of the cost of the _closure_: Your code
example suggests that you might use a plain function or a
pre-pepared closure instead of recreating the closure on
each request.
Before worrying to much about time spent in creating the
closure or even the once.Do: remember to measure the
costs. I admit I have no idea what the runtime cost of
creating a closure is, but peeking at [1] I think call to
once.Do should not trouble you.
On Thursday, May 31, 2012 10:29:41 AM UTC-4, Volker Dobler wrote:On May 31, 4:06 pm, Ugorji Nwoke <ugo...@gmail.com> wrote:
> My question was really:
> - In the case of a struct, is it possible for one goroutine to see the
> write to a member (initErr) without seeing the write to another member
> (inited), where both writes were done within a sync package call (once.Do
> here which uses mutexes internally).
To my (limited) understanding: Yes, this can happen. The
once.Do prevents the multiple execution of its argument (e.g.
by different goroutines) but it does not synchronize read/write
access to your struct, just because this struct contains a
sync.Once.
It's not "... because the struct contains a sync.Once", but more because the writes to the 2 fields are surrounded by mutex.lock/unlock (which sync.Once does).
Why can't you just call once.Do? Is the function call overhead too much? It does the moral eqivalent of your if (init) { do } itself.
FWIW, I ended up basically recreating once.Do functionality (all 10 lines of it) to do my use-case (once is basically a struct of a inited variable and a mutex).Also, I read more about CPU load/store/compare-and-swap/test-and-set operations and memory fence/barrier and I think I understand the model now.My code is now basically:if atomic.LoadUint32(&gapp.inited) == 0 {func() {gapp.initMutex.Lock()defer gapp.initMutex.Unlock()if gapp.inited == 0 {gapp.onceInitErr = gapp.InitFn(c, w, r)atomic.CompareAndSwapUint32(&gapp.inited, 0, 1)
On Friday, June 1, 2012 3:06:02 AM UTC+4, Ugorji Nwoke wrote:FWIW, I ended up basically recreating once.Do functionality (all 10 lines of it) to do my use-case (once is basically a struct of a inited variable and a mutex).Also, I read more about CPU load/store/compare-and-swap/test-and-set operations and memory fence/barrier and I think I understand the model now.My code is now basically:if atomic.LoadUint32(&gapp.inited) == 0 {func() {gapp.initMutex.Lock()defer gapp.initMutex.Unlock()if gapp.inited == 0 {gapp.onceInitErr = gapp.InitFn(c, w, r)atomic.CompareAndSwapUint32(&gapp.inited, 0, 1)This is already atomic.StoreUint32(&gapp.inited, 1) at tip.
On Friday, June 1, 2012 12:18:45 AM UTC-4, Dmitry Vyukov wrote:
On Friday, June 1, 2012 3:06:02 AM UTC+4, Ugorji Nwoke wrote:FWIW, I ended up basically recreating once.Do functionality (all 10 lines of it) to do my use-case (once is basically a struct of a inited variable and a mutex).Also, I read more about CPU load/store/compare-and-swap/test-and-set operations and memory fence/barrier and I think I understand the model now.My code is now basically:if atomic.LoadUint32(&gapp.inited) == 0 {func() {gapp.initMutex.Lock()defer gapp.initMutex.Unlock()if gapp.inited == 0 {gapp.onceInitErr = gapp.InitFn(c, w, r)atomic.CompareAndSwapUint32(&gapp.inited, 0, 1)This is already atomic.StoreUint32(&gapp.inited, 1) at tip.True. I used CompareAndSwap... to be consistent with the once.Do(...) implementation.
On Friday, June 1, 2012 12:18:45 AM UTC-4, Dmitry Vyukov wrote:
On Friday, June 1, 2012 3:06:02 AM UTC+4, Ugorji Nwoke wrote:FWIW, I ended up basically recreating once.Do functionality (all 10 lines of it) to do my use-case (once is basically a struct of a inited variable and a mutex).Also, I read more about CPU load/store/compare-and-swap/test-and-set operations and memory fence/barrier and I think I understand the model now.My code is now basically:if atomic.LoadUint32(&gapp.inited) == 0 {func() {gapp.initMutex.Lock()defer gapp.initMutex.Unlock()if gapp.inited == 0 {gapp.onceInitErr = gapp.InitFn(c, w, r)atomic.CompareAndSwapUint32(&gapp.inited, 0, 1)This is already atomic.StoreUint32(&gapp.inited, 1) at tip.True. I used CompareAndSwap... to be consistent with the once.Do(...) implementation.Once.Do uses StoreUint32.
Can you not pass the init function on a (buffered?) channel, then have every request attempt to read from it and have the successful reader close it? I've used this pattern a couple of times. I'm not sure of the overhead though. Or if it is actually safe. I think it is...
It's actually a really similar solution, and kinda amusing in and of itself :).var once = make(chan func(*http.Request), 1)func init() {once <- func(r *http.Request) { ... }}func handler(w http.ResponseWriter, r *http.Request) {if f := <-once; f != nil {f(r)close(once)}...
}
On Friday, June 1, 2012 8:17:58 PM UTC-4, si guy wrote:Can you not pass the init function on a (buffered?) channel, then have every request attempt to read from it and have the successful reader close it? I've used this pattern a couple of times. I'm not sure of the overhead though. Or if it is actually safe. I think it is...
It's actually a really similar solution, and kinda amusing in and of itself :).var once = make(chan func(*http.Request), 1)func init() {once <- func(r *http.Request) { ... }}func handler(w http.ResponseWriter, r *http.Request) {if f := <-once; f != nil {f(r)close(once)}...}
Channel close and observing that the channel is closed are not synchronization actions, they do not synchronize any data. ThreadSanitizer may say that you have a data race here on the data initialized by the func. And I believe it will actually broke on weakly ordered archs like ARM/POWER with current chans impl.
But Dmitry's
Russ
> But Dmitry's response was about data synchronization, which is a big part of
> what the init func does i.e. you want data written to in the init function
> to be visible by other goroutines. If channel close, etc do not synchronize
> any data, then you haven't really solved the problem.
If you close the channel only after the init function is executed then
you are sure that all other goroutines will wait for the value or the
close event.
On Monday, June 4, 2012 3:54:41 PM UTC-4, André Moraes wrote:> But Dmitry's response was about data synchronization, which is a big part of
> what the init func does i.e. you want data written to in the init function
> to be visible by other goroutines. If channel close, etc do not synchronize
> any data, then you haven't really solved the problem.
If you close the channel only after the init function is executed then
you are sure that all other goroutines will wait for the value or the
close event.
key word there is "data written to in the init function". ie assume init function is like://simplifying ...var ExportedVar1, ExportedVar2, var3 int64func init(r *http.Request) {ExportedVar1, ExportedVar2, var3 = 7, 9, 12}Does the close event guarantee that ExportedVar1, ExportedVar2, var3 are visible to other goroutines?The memory model does not spell this out.It spells out that once.Do(...) guarantees that. It spells out that using (RW)Mutex on read and write of variables guarantees that.
key word there is "data written to in the init function". ie assume init function is like://simplifying ...var ExportedVar1, ExportedVar2, var3 int64func init(r *http.Request) {ExportedVar1, ExportedVar2, var3 = 7, 9, 12}Does the close event guarantee that ExportedVar1, ExportedVar2, var3 are visible to other goroutines?
The memory model does not spell this out.
On Monday, June 4, 2012, Ugorji Nwoke wrote:key word there is "data written to in the init function". ie assume init function is like://simplifying ...var ExportedVar1, ExportedVar2, var3 int64func init(r *http.Request) {ExportedVar1, ExportedVar2, var3 = 7, 9, 12}Does the close event guarantee that ExportedVar1, ExportedVar2, var3 are visible to other goroutines?Yes.The memory model does not spell this out.This is mistaken. The memory model is very clear here. Closing the channel happens before the resulting zero receives. Everything that locally happens before closing the channel in the closing goroutine happens before the zero value is received in the receiving goroutine(s).
key word there is "data written to in the init function". ie assume init function is like://simplifying ...var ExportedVar1, ExportedVar2, var3 int64func init(r *http.Request) {ExportedVar1, ExportedVar2, var3 = 7, 9, 12}Does the close event guarantee that ExportedVar1, ExportedVar2, var3 are visible to other goroutines?Yes.The memory model does not spell this out.This is mistaken. The memory model is very clear here. Closing the channel happens before the resulting zero receives. Everything that locally happens before closing the channel in the closing goroutine happens before the zero value is received in the receiving goroutine(s).Ah. okay. Missed that before. Got it now.Have you read this: http://golang.org/ref/mem#tmp_69? It specifically makes the guarantee you're looking for.