ah yes, no, if you see the code in the play link below, it only has three 
channels, ops, done and ready. I just figured out that I replaced that 
ready by putting the close in the clause that processes incoming ops, and 
it's unused as well. I managed to trim it down to just one channel, the ops 
channel, and the done uses a 'comma ok' check and breaks when that produces 
a false value, otherwise it pushes the op back on the channel in case it 
was meant for the main loop: https://play.golang.org/p/zuNAJvwRlf-

On Friday, 3 May 2019 02:50:42 UTC+2, Louki Sumirniy wrote:
>
> oh, I did forget one thing. The race detector does not flag a race in this 
> code: https://play.golang.org/p/M1uGq1g4vjo (play refuses to run it 
> though)
>
> As I understand it, that's because the add/subtract operations are 
> happening serially within the main handler goroutine. I suppose if I were 
> to change my 'worker count' query to just print the value right there in 
> the select statement, the race would disappear, but that's not *that* 
> convenient. Though it covers the case of debugging, really. But how is it 
> not still a race since the data is being copied to send to a TTY?
>
> It would be quite handy, though, if one could constrain the race detector 
> by telling the compiler somehow that 'this goroutine owns that variable' 
> and any reads are ignored. It isn't really exactly a race condition to 
> sample the state at any given moment to give potentially useful information 
> to the caller. 
>
> On Friday, 3 May 2019 02:39:09 UTC+2, Louki Sumirniy wrote:
>>
>> I more or less eventually figured that out since it is impossible to 
>> query the number of workers without a race anyway, and then I started 
>> toying with atomic.Value and made that one race as well (obviously the 
>> value was copied by fmt.Println). I guess keeping track of the number of 
>> workers is on the caller side not on the waitgroup side, the whole thing is 
>> a black box because of the ease with which race conditions can arise when 
>> you let things inside the box. 
>>
>> The thing that I find odd though, is it is impossible to not trip the 
>> race detector, period, when copying that value out, it sees where it goes. 
>> The thing is that in the rest of the library, no operation on the worker 
>> counter triggers the race, I figure that's because it's one goroutine and 
>> the other functions are separate. As soon as the internal value crosses 
>> outwards as caller adds and subtracts workers concurrently, it is racy, but 
>> I don't see how reading a maybe racy value itself is racy if I am not going 
>> to do anything other than tell the user how many workers are running at a 
>> given moment. It wouldn't be to make any concrete, immediate decision to 
>> act based on this. Debugging is a prime example of when you want to read 
>> racy data but have no need to write back where it is being rendered to the 
>> user.
>>
>> Ah well, newbie questions. I think that part of the reason why for many 
>> people goroutines and channels are so fascinating is about concurrency, but 
>> not just concurrency, distributed processing more so. Distributed systems 
>> need concurrent and asynchronous responses to network activity, and 
>> channels are a perfect fit for eliminating context switch overhead from 
>> operations that span many machines.
>>
>> On Friday, 3 May 2019 01:33:53 UTC+2, Robert Engels wrote:
>>>
>>> Channels use sync primitives under the hood so you are not saving 
>>> anything by using multiple channels instead of a single wait group. 
>>>
>>> On May 2, 2019, at 5:57 PM, Louki Sumirniy <louki.sumi...@gmail.com> 
>>> wrote:
>>>
>>> As I mentioned earlier, I wanted to see if I could implement a waitgroup 
>>> with channels instead of the stdlib's sync.Atomic counters, and using a 
>>> special type of concurrent datatype called a PN Converged Replicated 
>>> Datatype. Well, I'm not sure if this implementation precisely implements 
>>> this type of CRDT, but it does work, and I wanted to share it. Note that 
>>> play doesn't like these long running (?) examples, so here it is verbatim 
>>> as I just finished writing it:
>>>
>>> package chanwg
>>>
>>> import "fmt"
>>>
>>> type WaitGroup struct {
>>>     workers uint
>>>     ops chan func()
>>>     ready chan struct{}
>>>     done chan struct{}
>>> }
>>>
>>> func New() *WaitGroup {
>>>     wg := &WaitGroup{
>>>         ops: make(chan func()),
>>>         done: make(chan struct{}),
>>>         ready: make(chan struct{}),
>>>     }
>>>     go func() {
>>>         // wait loop doesn't start until something is put into thte
>>>         done := false
>>>         for !done {
>>>             select {
>>>             case fn := <-wg.ops:
>>>                 println("received op")
>>>                 fn()
>>>                 fmt.Println("num workers:", wg.WorkerCount())
>>>                 // if !(wg.workers < 1) {
>>>                 //  println("wait counter at zero")
>>>                 //  done = true
>>>                 //  close(wg.done)
>>>                 // }
>>>             default:
>>>             }
>>>         }
>>>
>>>     }()
>>>     return wg
>>> }
>>>
>>> // Add adds a non-negative number
>>> func (wg *WaitGroup) Add(delta int) {
>>>     if delta < 0 {
>>>         return
>>>     }
>>>     fmt.Println("adding", delta, "workers")
>>>     wg.ops <- func() {
>>>         wg.workers += uint(delta)
>>>     }
>>> }
>>>
>>> // Done subtracts a non-negative value from the workers count
>>> func (wg *WaitGroup) Done(delta int) {
>>>     println("worker finished")
>>>     if delta < 0 {
>>>         return
>>>     }
>>>     println("pushing op to channel")
>>>     wg.ops <- func() {
>>>         println("finishing")
>>>         wg.workers -= uint(delta)
>>>     }
>>>     // println("op should have cleared by now")
>>> }
>>>
>>> // Wait blocks until the waitgroup decrements to zero
>>> func (wg *WaitGroup) Wait() {
>>>     println("a worker is waiting")
>>>     <-wg.done
>>>     println("job done")
>>> }
>>>
>>> func (wg *WaitGroup) WorkerCount() int {
>>>     return int(wg.workers)
>>> }
>>>
>>>
>>> There could be some bug lurking in there, I'm not sure, but it runs 
>>> exactly as I want it to, and all the debug prints show you how it works.
>>>
>>> Possibly one does not need to use channels containing functions that 
>>> mutate the counter, but rather maybe they can be just directly 
>>> increment/decremented within a select statement. I've gotten really used to 
>>> using generator functions and they seem to be extremely easy to use and so 
>>> greatly simplify and modularise my code that I am now tackling far more 
>>> complex (if cyclomatic complexity is a measure - over 130 paths in a menu 
>>> system I wrote that uses generators to parse a declaration of data types 
>>> that also uses generators).
>>>
>>> I suppose the thing is it wouldn't be hard to extend the types of 
>>> operations that you push to the ops  channel, I can't think off the top of 
>>> my head exactly any reasonable use case for some other operation though. 
>>> One thing that does come to mind, however, is that a more complex, 
>>> conditional increment operation could be written and execute based on other 
>>> channel signals or the state of some other data, but I can't see any real 
>>> use for that.
>>>
>>> I should create a benchmark that tests the relative performance of this 
>>> versus sync.Atomic add/subtract operations. I think also that as I 
>>> mentioned, changing the ops channel to just contain deltas on the group 
>>> size might be a little bit faster than the conditional jumps a closure 
>>> requires to enter and exit.
>>>
>>> So the jury is out still if this is in any way superior to 
>>> sync.WaitGroup, but because I know that this library does not use channels 
>>> that it almost certainly has a little higher overhead due to the function 
>>> call context switches hidden inside the Atomic increment/decrement 
>>> operations.
>>>
>>> Because all of those ops occur within the one supervisor waitgroup 
>>> goroutine only, they are serialised automatically by the channel buffer (or 
>>> the wait sync as sender and receiver both become ready), and no 
>>> atomic/locking operations are required to prevent a race.
>>>
>>> I enabled race detector on a test of this code just now. The 
>>> WorkerCount() function is racy. I think I need to change it so there is a 
>>> channel underlying the retrieval implementation, it then would send the 
>>> (empty) query to the query channel, and listen on an answer channel (maybe 
>>> make them one-direction) to get the value without an explicit race.
>>>
>>> Yes, and this is probably why sync.WaitGroup has no way to inspect the 
>>> current wait count also. I will see if I can make that function not racy.
>>>
>>> On Thursday, 2 May 2019 23:29:35 UTC+2, Øyvind Teig wrote:
>>>>
>>>> Thanks for the reference to Dave Cheney's blog note! And for this 
>>>> thread, quite interesting to read. I am not used to explicitly closing 
>>>> channels at all (occam (in the ninetees) and XC (now)), but I have sat 
>>>> through several presentations on conferences seen the theme being 
>>>> discussed, like with the JCSP library. I am impressed by the depth of the 
>>>> reasoning done by the Go designers!
>>>
>>> -- 
>>> 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 golan...@googlegroups.com.
>>> 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