sync.Once and initialization. Is below safe based on Go's memory model?

2,449 views
Skip to first unread message

Ugorji Nwoke

unread,
May 31, 2012, 9:24:08 AM5/31/12
to golan...@googlegroups.com

if !inited {
  once.Do(func() {
    initErr = doInit()
    inited = true
  })
}

if initErr != nil {
  // ...
}

I've been trying to understand the Go memory model specifically for this use-case: http://golang.org/ref/mem

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. 

Can someone give me a definitive answer?

Thanks.

Ugorji Nwoke

unread,
May 31, 2012, 9:29:59 AM5/31/12
to golan...@googlegroups.com
And I'm asking this specifically in the situation where there are multiple cores, GOMAXPROCS > 1 and many concurrent goroutines.

Ian Lance Taylor

unread,
May 31, 2012, 9:52:31 AM5/31/12
to Ugorji Nwoke, golan...@googlegroups.com
This kind of code is not valid according to the Go memory model. One
goroutine can read "inited" and another can write it, but there is no
happens-before relationship between the two goroutines.

That said, I can't think of anything that could really go wrong here.

And that said, the cost of a closure is fairly low, I wouldn't worry
about this unless this function is very hot.

Not to miss the obvious, could you simply write

func init() {
initErr = doInit()
}

Ian

Ugorji Nwoke

unread,
May 31, 2012, 9:52:43 AM5/31/12
to yy, golan...@googlegroups.com
I understand what once.Do gives you (that it will call the passed closure once and only once). However, I want to avoid creating the closure and calling once.Do every time (just for it to be a no-op)

My question is really about the memory model guarantees in such a situation. Or is my only safe recourse to have a closure created and once.Do called every request?

Thanks.

Ugorji
An eagle, aspiring for greater heights ...



On Thu, May 31, 2012 at 9:44 AM, yy <yiyu...@gmail.com> wrote:
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 .

Peter S

unread,
May 31, 2012, 9:55:07 AM5/31/12
to Ugorji Nwoke, golan...@googlegroups.com
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

Ugorji Nwoke

unread,
May 31, 2012, 9:57:01 AM5/31/12
to golan...@googlegroups.com
Thanks Ian. That's what I suspected.

I can't do this within an init() function, because this initialization it is called within the first http request and needs access to that http.Request variable.

To be safe, I'd remove the inited and just use a have once.Do(...) be called everytime.

Ugorji Nwoke

unread,
May 31, 2012, 10:06:19 AM5/31/12
to golan...@googlegroups.com
Yes, my example was not precise (I apologize).

I saw that example, but my real question was in the case of structs. I simplified it before sending the initial post, not realizing I was removing a key part of my question.

It's really more like:

type App struct {
  inited bool
  initErr error
  once sync.Once
}

func serveHTTP(w http.ResponseWriter, r *http.Request) {
  if !app.inited {
    app.once.Do(func() {
      app.initErr = doInit()
      app.inited = true
    })
  }

  if app.initErr != nil {
    // ...
  }
}

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).

On Thursday, May 31, 2012 9:55:07 AM UTC-4, speter wrote:
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

André Moraes

unread,
May 31, 2012, 10:17:54 AM5/31/12
to Ugorji Nwoke, golan...@googlegroups.com
Given the cost of:

Your code inside the handler
The HTTP parsing code
The TCP stack cost.

Calling once.Do many times won't generate soo much overhead.

The other alternative would be to:
Use a your own mutex.
Define one handler that execute the init code and calls the handler to
do the real work.
Define another handler that don't execute the init code and calls the
handler to do the real work.

The second alternative is more costly and more complex.



--
André Moraes
http://amoraes.info

Ugorji Nwoke

unread,
May 31, 2012, 10:29:38 AM5/31/12
to golan...@googlegroups.com
Thanks Andre.

I understand the relative cost and the tradeoff. 

Regardless of the cost of other operations, I don't like to do busy-work "where I can help it" (e.g. creating a closure each time and calling sync operations each time for what ends up being a noop). That's why I just wanted to know the guarantees of the memory model in this situation, especially in the case of struct fields which were updated within a sync operation.

I'd go with Ian's answer for now that the Go memory model doesn't guarantee it, so every call will have to synchronize access to those variables. Based on that, I'd just have each request call once.Do(...).

Volker Dobler

unread,
May 31, 2012, 10:29:41 AM5/31/12
to golang-nuts
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.

Volker

[1] http://golang.org/src/pkg/sync/once.go?s=1040:1067#L22

Ugorji Nwoke

unread,
May 31, 2012, 10:41:11 AM5/31/12
to golan...@googlegroups.com


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).
 
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.

I can't use a prepared closure because the http.Request variable (which the closure depends on) is different each time.
 
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.

I'm not so much concerned about the cost as understanding the guarantees. That's more important, and can inform how I write my code, as opposed to "just wrap your call in once.Do for every request, ... because it's not really expensive relatively". 

Peter S

unread,
May 31, 2012, 11:42:54 AM5/31/12
to Ugorji Nwoke, golan...@googlegroups.com
On Thu, May 31, 2012 at 11:41 PM, Ugorji Nwoke <ugo...@gmail.com> wrote:


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).

In my interpretation, the gist is that in order to establish observability (happens-before relation), synchronization has to happen both in the writing and reading goroutines. So it is not sufficient to surround writes only with synchronization.

IIUC, the specific issue in your example would be the possibility that initialization set inited to true and initErr to a non-nil value, but some goroutines observe inited == true and initErr == nil. (The opposite, that is observing inited == false and initErr != nil, can also happen in theory, but it doesn't appear to me to cause any inconsistent behavior in the posted example. It may cause issues with more complex code though.)

It may be argued that there is some ambiguity regarding the use of "variable" in the memory model when considering structs, but I think things should be clearer if you think in terms of read and write operations.

Peter

Ugorji Nwoke

unread,
May 31, 2012, 11:51:37 AM5/31/12
to golan...@googlegroups.com
Thanks. Makes sense.

I understood this much, but was holding out hope of some edge cases wrt struct fields. I'm fine with doing the once.Do(...) always.

Thanks.

David Klaffenbach

unread,
May 31, 2012, 3:43:01 PM5/31/12
to golan...@googlegroups.com
Why wouldn't you refactor the code so that the initialization function would be done before launching the goroutines that need it?  That way you wouldn't need all the sync.Once calls over and over again.

Ugorji Nwoke

unread,
May 31, 2012, 4:01:12 PM5/31/12
to golan...@googlegroups.com
For my application, the initialization code needs to be done within the context of a http request. So we have to do it on the first request.

An similar application environment which needs this is the app engine go environment, where all service calls need a context which is bound to a http request. To do initialization that requires service calls, it must be done on first request, and done only once.

Kyle Lemons

unread,
May 31, 2012, 4:13:01 PM5/31/12
to Ugorji Nwoke, golan...@googlegroups.com
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.

Ugorji Nwoke

unread,
May 31, 2012, 4:27:46 PM5/31/12
to golan...@googlegroups.com
Because dynamic closures are not free:

And once.Do(func() { ... }) will dynamically (at runtime) create a closure for every request which will never be called except the first time.

if !inited { ... } will only cause the closure to be created and called the first time only.

The option is to basically do what once.Do does myself (it's only like 7 lines basic mutex/atomic calls), and not create closure, etc.

On Thursday, May 31, 2012 4:13:01 PM UTC-4, Kyle Lemons wrote:
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.

Nigel Tao

unread,
May 31, 2012, 6:40:13 PM5/31/12
to Ugorji Nwoke, golan...@googlegroups.com
On 31 May 2012 23:57, Ugorji Nwoke <ugo...@gmail.com> wrote:
> I can't do this within an init() function, because this initialization it is
> called within the first http request and needs access to that http.Request
> variable.

If this is App Engine, you could use a warmup request.

https://developers.google.com/appengine/docs/adminconsole/instances#Warmup_Requests

Ugorji Nwoke

unread,
May 31, 2012, 6:43:48 PM5/31/12
to golan...@googlegroups.com, Ugorji Nwoke
Thanks Nigel.

It's actually not appengine (I just used app engine as example to illustrate this). Nevertheless, with app engine, warmup requests are not guaranteed to be run, so init code cannot depend on it. 

Ugorji Nwoke

unread,
May 31, 2012, 7:06:02 PM5/31/12
to golan...@googlegroups.com
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)
}
}()
}
if gapp.onceInitErr != nil {
return gapp.onceInitErr
}
// ...

Dmitry Vyukov

unread,
Jun 1, 2012, 12:18:45 AM6/1/12
to golan...@googlegroups.com


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.

Ugorji Nwoke

unread,
Jun 1, 2012, 5:24:18 AM6/1/12
to golan...@googlegroups.com


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. 

Dmitry Vyukov

unread,
Jun 1, 2012, 5:33:55 AM6/1/12
to Ugorji Nwoke, golan...@googlegroups.com
On Fri, Jun 1, 2012 at 1:24 PM, Ugorji Nwoke <ugo...@gmail.com> wrote:


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.

Ugorji Nwoke

unread,
Jun 1, 2012, 5:43:29 AM6/1/12
to golan...@googlegroups.com


On Friday, June 1, 2012 5:33:55 AM UTC-4, Dmitry Vyukov wrote:


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.

Ah. You're right. That change went in a few weeks ago. I'd update mine also. Thanks. 

André Moraes

unread,
Jun 1, 2012, 9:49:33 AM6/1/12
to Ugorji Nwoke, golan...@googlegroups.com
Why don't you use:

http://go.pkgdoc.org/code.google.com/p/gorilla/context

You can check if your data is in the store and if not you run the init code.

The context is thread-safe and you can use it for many other things. I
use it to store mgo sessions for each request.

Ugorji Nwoke

unread,
Jun 1, 2012, 9:57:31 AM6/1/12
to golan...@googlegroups.com, Ugorji Nwoke
I don't think this will help.

The initialization code must be run only once. Many concurrent requests may see it has not been run i.e. that the data is not in the container (e.g. gorilla context store) causing many goroutines to run initialization. Synchronizing (what once.Do does, or what mutex can do along with a variable) seems to be the only way. 

si guy

unread,
Jun 1, 2012, 8:17:58 PM6/1/12
to golan...@googlegroups.com
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...

Ugorji Nwoke

unread,
Jun 1, 2012, 8:25:27 PM6/1/12
to golan...@googlegroups.com


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 wouldn't be a solution for this use case, where we need context from a request (function parameters passed up the chain) to do the initialization. For a general case e.g. with a predefined named function, I don't think you can do much better than once.Do.

Kyle Lemons

unread,
Jun 1, 2012, 8:41:47 PM6/1/12
to Ugorji Nwoke, golan...@googlegroups.com
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)
  }
  ...

Ugorji Nwoke

unread,
Jun 1, 2012, 8:50:01 PM6/1/12
to golan...@googlegroups.com, Ugorji Nwoke
Ah! Nice. Beautiful. My apologies to si guy for missing the elegance in his suggestion. 


On Friday, June 1, 2012 8:41:47 PM UTC-4, Kyle Lemons wrote:
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...

Dmitry Vyukov

unread,
Jun 4, 2012, 10:05:46 AM6/4/12
to Kyle Lemons, Ugorji Nwoke, golan...@googlegroups.com
On Fri, Jun 1, 2012 at 8:41 PM, Kyle Lemons <kev...@google.com> wrote:
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)
  }
  ...
}


That's broken under Go Memory Model.
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.


André Moraes

unread,
Jun 4, 2012, 11:28:28 AM6/4/12
to Ugorji Nwoke, golan...@googlegroups.com
>>
>> 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)
>>   }
>>   ...
>> }
>>

Maybe instead of a buffered channel, use a non-buffered-channel

type Fn func(req *http.Request)
var init = make(chan Fn)

func myHandler(w http.ResponseWriter, req *http.Request) {
if fn, ok := <-init; ok {
// the channel was open
// run the fn for the first time
fn(req)
}
// here the channel was closed or the init function executed without
any problem.
}

func main() {
http.HandleFunc("/", myHandler)
go func() {
init <- func(req *http.Request) {
// do something here
}
close(init)
}()
http.ListenAndServe(port, nil)

Kyle Lemons

unread,
Jun 4, 2012, 12:12:37 PM6/4/12
to Dmitry Vyukov, Ugorji Nwoke, golan...@googlegroups.com
The way I read it, the close operation may not synchronize any data, but the memory model specifies that the close "happens before" the zero-receives:
The closing of a channel happens before a receive that returns a zero value because the channel is closed.

So, am I reading that wrong?

Ian Lance Taylor

unread,
Jun 4, 2012, 12:46:31 PM6/4/12
to Dmitry Vyukov, Kyle Lemons, Ugorji Nwoke, golan...@googlegroups.com
Dmitry Vyukov <dvy...@google.com> writes:

> That's broken under Go Memory Model.
> Channel close and observing that the channel is closed are not
> synchronization actions, they do not synchronize any data.

I think that is a bug in the Go memory model. Channel close should be
handled like sending data on a channel.

Ian

Steven Blenkinsop

unread,
Jun 4, 2012, 2:10:38 PM6/4/12
to Ian Lance Taylor, Dmitry Vyukov, Kyle Lemons, Ugorji Nwoke, golan...@googlegroups.com
It was broken, but it was specifically fixed (I remember, I brought the issue up, though I don't claim to be the first). The part Kyle quoted was added specifically the make this clear.

Russ Cox

unread,
Jun 4, 2012, 2:24:34 PM6/4/12
to Steven Blenkinsop, Ian Lance Taylor, Dmitry Vyukov, Kyle Lemons, Ugorji Nwoke, golan...@googlegroups.com
I think the memory model is correct as is (and Dmitry's claim is
incorrect). You find out that the channel is closed by doing a receive
that gets a zero value, and then the part that Kyle quoted applies.

Russ

Ugorji Nwoke

unread,
Jun 4, 2012, 2:37:48 PM6/4/12
to golan...@googlegroups.com, Steven Blenkinsop, Ian Lance Taylor, Dmitry Vyukov, Kyle Lemons, Ugorji Nwoke, r...@golang.org
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.

Quoting Dmitry's response again:
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

André Moraes

unread,
Jun 4, 2012, 3:54:41 PM6/4/12
to Ugorji Nwoke, golan...@googlegroups.com
> 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.

A read on a empty channel will block until data is available, the
channel is closed or you used a select with default.

Ugorji Nwoke

unread,
Jun 4, 2012, 4:51:38 PM6/4/12
to golan...@googlegroups.com, Ugorji Nwoke


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 int64
func 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. 

Kyle Lemons

unread,
Jun 4, 2012, 5:08:44 PM6/4/12
to Ugorji Nwoke, golan...@googlegroups.com
On Mon, Jun 4, 2012 at 1:51 PM, Ugorji Nwoke <ugo...@gmail.com> wrote:


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 int64
func 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. 

Yes.  If the assignment "happens before" the close, and the zero read "happens before" you read the assignment, you are guaranteed to observe the write (assuming nobody else changes it).  In particular:

var once = make(chan func(*http.Request), 1) // A

func init() {
  once <- func(r *http.Request) { ... } // B
}

func handler(w http.ResponseWriter, r *http.Request) {
  if f := <-once; f != nil { // C
    f(r) // F
    close(once) // D
  }
  ...
  // E
}

My attempt at a proof:

A happens before B because it's a global initialization.  In the case that "f != nil" (which I'll call _0), B happens before C_0 because either init runs or (in the paranoid case) the recv blocks until it does.  In the case that "f == nil" (which I'll call _1), the memory model specifies that the close happens before the zero receive, so D_0 happens before C_1.  Because ( D_0 happens before E_0 ) and ( D_0 happens before C_1 which happens before E_1 ) and F happens before D, F happens before E in all cases.

Steven Blenkinsop

unread,
Jun 4, 2012, 5:16:36 PM6/4/12
to Ugorji Nwoke, golan...@googlegroups.com
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 int64
func 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). 

? It specifically makes the guarantee you're looking for.

Ugorji Nwoke

unread,
Jun 4, 2012, 5:16:47 PM6/4/12
to golan...@googlegroups.com, Ugorji Nwoke


On Monday, June 4, 2012 5:08:44 PM UTC-4, Kyle Lemons wrote:
Your explanation just shows that the init function gets called, but not that variables written in said init function (ie in f(r), at stage E in your illustration) are visible to other goroutines once close(onceChan) is called. (see http://golang.org/ref/mem). i.e. it's guaranteed that f(r) gets called but not that V1, V2, v3 which were written to in f(r) are visible to other goroutines which see that onceChan is closed.

Ugorji Nwoke

unread,
Jun 4, 2012, 5:20:14 PM6/4/12
to golan...@googlegroups.com, Ugorji Nwoke


On Monday, June 4, 2012 5:16:36 PM UTC-4, Steven Blenkinsop wrote:
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 int64
func 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.

Dmitry Vyukov

unread,
Jun 5, 2012, 8:56:06 AM6/5/12
to Ugorji Nwoke, golan...@googlegroups.com
On Mon, Jun 4, 2012 at 5:20 PM, Ugorji Nwoke <ugo...@gmail.com> wrote:

key word there is "data written to in the init function". ie assume init function is like:

//simplifying ... 
var ExportedVar1, ExportedVar2, var3 int64
func 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.
 
? It specifically makes the guarantee you're looking for.


Oh, sorry, I had to consult the doc before writing that nonsense. Was it always there? Did I confuse close and len?

Reply all
Reply to author
Forward
0 new messages