Roger, it is not that I wondering it is async assignments are bad, it is 
more of a question "why does it have to be SO complicated"? 

(please forgive me for a long ranty post)

There were couple of things I was implementing for our little in-house 
server app. 
One was (in)famous fan-out pattern for broadcasting messages to clients, 
and one for job queue(s) that could run fast (seconds) or long (hours, 
days) where items kind of coming it from multiple (same) clients.

While implementing notifications part, I have eaten my learning salty cake 
of naivety with lockings and crashes on top. So second (jobs control) was 
not using channels in any form at all. (I guess I also got influenced by 
looking into Go runtime and 3rd party libraries more and more over time).

Lessons that I got from that exercise are
1. In order to work with a channel that could go away - close()d and/or set 
to nil - you gotta have a second (control) channel for notifying that this 
thing is dead or is on a life support (shutting down). "Full sync for 
async!". Whole experience left very sad experience of writing TCP-like 
"SEND-->ACK-->ACK of the ACK" versus simple RST (aka close/nil) over and 
over. close() itself does not work well as a nice simple notification of 
"done", it needs sync and be protected against "done-done".

2. No matter how much you check for nil even with synchronization 
mechanism, you gotta get surprises at shutdown time! That's when things are 
in the unstoppable avalanche of "I am going away!" broadcasts and close() 
calls. Oh, and because shutdown is unstoppable, you cannot lock the house 
for "lets wait for something!" party anymore. So, no close() then. 

3. Unless you absolutely need to close a channel (so it serves as 
notification mechanism for "range" loops), leave them alone and hope they 
got decomposed by the worms of GC.

4. close() itself is not protected by any barriers on entry, so even if you 
checked it for nil or did a funky 'select' dance to check if it is closed 
already, you gotta get surprises without full sync access to the channel 
variable itself. 

The experience? Too much mental gymnastics over it. The (sad) conclusion? 
It is not worth it, there are only so many hours of sleep left. So ...

5. I do not use channels -- too much hassle with controlling lifetime and 
state of a channel itself. Slice+mutex is much easier to argue about (and - 
bonus! - they can be regrown as needed). Yeah, we are losing nice "range" 
idiom, and select here and there, and beauty of a channels-as-sync 
mechanism.

I have had a bit of experience with other languages, but not Go before, so 
that was a learning experience for me. I wish that runtime would be more 
forgiving on closing the closed, and closing the nilled the same way it 
forgives nilling the nilled and nilling the closed.

I am wondering how much shorter https://blog.golang.org/pipelines article 
would be if "_ = close(c)" would be allowed, and used as "do not panic" way 
of "I am done" notifications.

Thank you very much and sorry for the rant,
   Andrey

On Wednesday, March 13, 2019 at 11:21:45 AM UTC-6, rog wrote:
>
> It might be helpful to have a better idea of what you're actually trying 
> to do here.
> Specifically, why do you feel the need to assign to c concurrently?
>
> On Wed, 13 Mar 2019 at 16:38, Andrey Tcherepanov <xnow4f...@sneakemail.com 
> <javascript:>> wrote:
>
>> Thank you Peter, I know that - I wrote that example on purpose to be 
>> undefined.
>>
>> To make it "well-defined" requires an enormous "fluff" which I am 
>> mentioned and trying to avoid.
>>
>> Andrey
>>
>> On Wednesday, March 13, 2019 at 9:02:53 AM UTC-6, peterGo wrote:
>>>
>>> Andrey,
>>>
>>> Your program has a data race. The results are undefined.
>>>
>>> Data Race Detector  https://golang.org/doc/articles/race_detector.html
>>>
>>> $ go run -race racer.go
>>> ==================
>>> WARNING: DATA RACE
>>> Write at 0x00000050dfa0 by goroutine 6:
>>>   main.niller()
>>>       /home/peter/gopath/src/racer.go:6 +0x3a
>>>
>>> Previous read at 0x00000050dfa0 by goroutine 5:
>>>   main.closer()
>>>       /home/peter/gopath/src/racer.go:10 +0x3a
>>>
>>> Goroutine 6 (running) created at:
>>>   main.main()
>>>       /home/peter/gopath/src/racer.go:18 +0xaa
>>>
>>> Goroutine 5 (finished) created at:
>>>   main.main()
>>>       /home/peter/gopath/src/racer.go:17 +0x92
>>> ==================
>>> panic: close of closed channel
>>>
>>> goroutine 1 [running]:
>>> main.main()
>>>     /home/peter/gopath/src/racer.go:23 +0xf4
>>> exit status 2
>>> $ 
>>>
>>> Peter
>>>
>>>
>>> On Wednesday, March 13, 2019 at 2:40:30 AM UTC-4, Andrey Tcherepanov 
>>> wrote:
>>>>
>>>> Hello fellow Go devs,
>>>>
>>>> I have a question that probably is a bit weird and obvious, but here we 
>>>> go
>>>>
>>>> package main
>>>>
>>>> var c chan int
>>>>
>>>> func niller() {
>>>> c = nil
>>>> }
>>>>
>>>> func closer() {
>>>> close(c)
>>>> }
>>>>
>>>> func main() {
>>>>
>>>> c = make(chan int, 1)
>>>>
>>>>
>>>> go closer()
>>>> go niller()
>>>>
>>>> // checkpoint-1
>>>> if c != nil {
>>>> // checkpoint-2
>>>> close(c)
>>>> c = nil
>>>> }
>>>> }
>>>>
>>>> What are the guarantees (if any) that c, being non-nil at checkpoint-1 
>>>> will not become a nil at checkpoint-2?
>>>>
>>>> My take on it that there are none, and the code needs to be fully 
>>>> synced around that close/nil.
>>>> But ... Is there any hard math theory around how `close()` MUST be 
>>>> implemented in order to have some guarantees about concurrency and 
>>>> consistency?
>>>>
>>>> (heavy IMHO warning) 
>>>> Current implementation of close() starts with 2 checks and panics, and 
>>>> the more I think of it, the less I am thrilled about both of them. They 
>>>> both causing me nothing but headache by pretty much requiring 2 channels 
>>>> everywhere code that could be much simpler with just 1 channel and no 
>>>> fluff. Implementing this "fluff" is error prone and I would add it is not 
>>>> a 
>>>> junior dev task, on whom Go seems to be (quite controversially IMHO)  is 
>>>> focused on.
>>>>
>>>> So... Did anybody ever proposed a second close() variant that returns 
>>>> an error instead of hard panic-ing inside?
>>>>
>>>> For example, if I have 
>>>>
>>>> err := close(c)
>>>>
>>>> it will not panic, but if I use just
>>>>
>>>> close(c)
>>>>
>>>> all bets are off, just like in a current Go code? I think this would be 
>>>> perfectly code-compatible with an "old" code, keeping Go1 compatibility 
>>>> guarantee untouched.
>>>>
>>>> Thank you very much,
>>>>    Andrey
>>>>
>>>> -- 
>> You received this message because you are subscribed to the Google Groups 
>> "golang-nuts" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to golang-nuts...@googlegroups.com <javascript:>.
>> For more options, visit https://groups.google.com/d/optout.
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to