these are excellent answers. i offer a harsher one:* the wrong answer faster is not optimization. *the law of programming has correctness at its core--imagine reasoning about a program where "if 5 < 7{stuff}" executed 50% of the time, or even 99.9999% of the time. if it was faster, that simply would not matter. this is what you cause then deny when you embrace race conditions of any kind, anywhere, ever. it means that your work only kind of works, in some cases, at some time, at the very best.
it is the software equivalent of saying that cracked bridges have "not all that many cracks." don't go there. not just for your reputation, but for users and that of careful programmers. On Sat, Mar 17, 2018 at 9:40 AM, <thepudds1...@gmail.com> wrote: > Hi all, > > In this particular case, this is a toy example of course, but for this toy > example, it is absolutely a case where the performance of the > synchronization primitive literally does not matter at all. (If I followed > here, the intent is seemingly to watch a long running task, and the example > has a 500ms sleep, etc.). > > That said, sometimes performance does matter. > > If instead this was a DIFFERENT example that was instead in some tight > performance critical inner loop, the performance of the synchronization > primitive for a stop flag can start to be meaningful (which of course > should first be demonstrated by your benchmarking and/or your profiling of > your particular program -- "premature optimization is the root of all evil" > and all that). > > Empirically speaking, that is then a situation where I've seen people > start to get into discussion around "well on amd64 you can rely on X and Y > so we can get away with a stop flag that doesn't use a mutex or an atomic", > and then people start talking about benign data races, and then other > people start talking about "there are no benign data races", and then > there's the reference to Dmitry Vyukov's article on benign data races, etc.: > > https://software.intel.com/en-us/blogs/2013/01/06/benign- > data-races-what-could-possibly-go-wrong > > To be honest I've seen it take up a fair amount of energy just to discuss, > and again empirically speaking I've seen very smart people make mistakes > here. > > One question I have regarding using an atomic for a stop flag is whether > or not there is material performance overhead compared to a simple > unprotected/non-atomic stop flag. > > I looked at that question a bit circa go ~1.7, so possibly stale info > here, and I haven't gone back to look at my more detailed notes, but if I > recall correctly I think my conclusion at the time was: > > 1. If your use case is a stop flag that will be checked many times (say, > in a tight inner loop), and the stop flag only gets set rarely, then the > performance of the set doesn't matter much, the assembly emitted for an > atomic load (such as atomic.LoadUint64) seems to be identical for the > assembly emitted for a simple load (say, *myUnint64Ptr) based on some spot > checking a while ago (with a sample of assembly from 1.9 pasted in at the > bottom of this post for comparison purposes). > > 2. Basic micro benchmarks didn't show a difference between an atomic load > and an unprotected load for a stop flag. > > 3. There might be some theoretical possible overhead due to the go > compiler will do instruction reordering in general, but won't do > instruction reordering across function calls, so that could be one > difference that might or might not make a difference depending on your > exact code (though likely modest impact)? Regarding this last point, I'm > just an interested spectator when it comes to the go compiler, so just to > be clear I don't really know this definitively. > > At least for us at the time, the quick test program & comparison of > assembly was enough to move us past the whole "Well, on amd64 you can get > away with X" discussion, so I didn't delve too much deeper than that at the > time. > > In any event, sharing this sample assembly with the community in case > anyone is interested and/or has additional commentary on the particulars > here in terms of performance impact in go of using an atomic load vs an > unprotected stop flag for the (admittedly somewhat rare) cases when the > nanoseconds do indeed matter. (And for me, a clean report from the race > detector trumps the performance arguments, but that doesn't mean I'm not > curious about the performance...). > > Here is a simple test program: > > https://play.golang.org/p/PaCQwb5m9ag > > func simpleLoadUint64(inputPtr *uint64) uint64 { > // normal load of *inputPtr > return *inputPtr > } > > func atomicLoadUint64(inputPtr *uint64) uint64 { > // atomic.LoadUint64 atomically loads *inputPtr > return atomic.LoadUint64(inputPtr) > } > > And here is the corresponding assembly snippets (from go 1.9): > > go build -gcflags -S atomic_vs_normal_load.go > > // trivial function with an unprotected load > > "".simpleLoadUint64 STEXT nosplit size=14 args=0x10 locals=0x0 > 0x0000 00000 (atomic_vs_normal_load.go:8) TEXT > "".simpleLoadUint64(SB), NOSPLIT, $0-16 > 0x0000 00000 (atomic_vs_normal_load.go:8) FUNCDATA $0, > gclocals·aef1f7ba6e2630c93a51843d99f5a28a(SB) > 0x0000 00000 (atomic_vs_normal_load.go:8) FUNCDATA $1, > gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) > 0x0000 00000 (atomic_vs_normal_load.go:8) MOVQ > "".inputPtr+8(SP), AX > 0x0005 00005 (atomic_vs_normal_load.go:10) MOVQ (AX), AX > 0x0008 00008 (atomic_vs_normal_load.go:10) MOVQ AX, > "".~r1+16(SP) > 0x000d 00013 (atomic_vs_normal_load.go:10) RET > 0x0000 48 8b 44 24 08 48 8b 00 48 89 44 24 10 c3 > H.D$.H..H.D$.. > > // trivial function with a sync/atomic LoadUint64: > > "".atomicLoadUint64 STEXT nosplit size=14 args=0x10 locals=0x0 > 0x0000 00000 (atomic_vs_normal_load.go:13) TEXT > "".atomicLoadUint64(SB), NOSPLIT, $0-16 > 0x0000 00000 (atomic_vs_normal_load.go:13) FUNCDATA $0, > gclocals·aef1f7ba6e2630c93a51843d99f5a28a(SB) > 0x0000 00000 (atomic_vs_normal_load.go:13) FUNCDATA $1, > gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) > 0x0000 00000 (atomic_vs_normal_load.go:13) MOVQ > "".inputPtr+8(SP), AX > 0x0005 00005 (atomic_vs_normal_load.go:15) MOVQ (AX), AX > 0x0008 00008 (atomic_vs_normal_load.go:15) MOVQ AX, > "".~r1+16(SP) > 0x000d 00013 (atomic_vs_normal_load.go:15) RET > 0x0000 48 8b 44 24 08 48 8b 00 48 89 44 24 10 c3 > H.D$.H..H.D$.. > --thepudds > > On Saturday, March 17, 2018 at 10:37:48 AM UTC-4, matthe...@gmail.com > wrote: > >> I think the second example alternative given (playground link above) has >>> a data race? >> >> >> I’m not surprised that the race detector sees something (a read can >> happen during a write of the checked bool) but I don’t think this could >> actually cause problems because the var’s memory value will always be 0 or >> 1. >> >> There may be implementation details or future implementation details that >> cause a problem though, so one option could be to protect the bool as a >> shared resource with a mutex or equivalent, but I think rog’s solution is >> better anyway (the first one). >> >> They all involve either repeatedly checking on a timer or checking the >>> value of another field (like polling) to see whether the long running task >>> should be stopped. >> >> >> Right, now every iteration of the loop has more work added. >> >> Using os.Cmd may be an option, where you can call Kill on the separate >> process. >> >> Matt >> >> On Saturday, March 17, 2018 at 8:01:33 AM UTC-5, thepud...@gmail.com >> wrote: >>> >>> *> "Here's another way: https://play.golang.org/p/gEDef3LolAZ >>>> <https://play.golang.org/p/gEDef3LolAZ> "* >>> >>> >>> Hi all, >>> >>> I think the second example alternative given (playground link above) has >>> a data race? >>> >>> Sample race detector run just now. (The two reports are inverses of each >>> other: read then write vs. write then read). >>> >>> ----------------------------------------------------------------------- >>> go run -race stop_flag_from_gonuts.go >>> >>> . . . . . quit sending ... >>> after quit sent================== >>> . >>> WARNING: DATA RACE >>> Write at 0x00c042072000 by goroutine 8: >>> main.f.func1() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:13 +0x59 >>> >>> Previous read at 0x00c042072000 by goroutine 6: >>> main.f() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:17 +0x102 >>> >>> Goroutine 8 (running) created at: >>> main.f() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:11 +0x8a >>> >>> Goroutine 6 (running) created at: >>> main.main() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:28 +0x70 >>> ================== >>> ================== >>> WARNING: DATA RACE >>> Read at 0x00c042072000 by goroutine 6: >>> main.f() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:17 +0x102 >>> >>> Previous write at 0x00c042072000 by goroutine 8: >>> main.f.func1() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:13 +0x59 >>> >>> Goroutine 6 (running) created at: >>> main.main() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:28 +0x70 >>> >>> Goroutine 8 (finished) created at: >>> main.f() >>> C:/cygwin64/bin/gonuts/stop_flag_from_gonuts.go:11 +0x8a >>> ================== >>> quit called >>> Found 2 data race(s) >>> exit status 66 >>> ----------------------------------------------------------------------- >>> >>> --thepudds >>> >>> On Friday, March 16, 2018 at 11:04:38 AM UTC-4, matthe...@gmail.com >>> wrote: >>>> >>>> While this is running, your select won't be receiving on the quit >>>>> channel, even if it is non-nil. >>>>> If you want to be able to cancel it, you'll need to make the code in >>>>> the loop responsive to the quit channel >>>>> (for example, by using a select like you're using in f already). >>>> >>>> >>>> The default select case does it: https://play.golang.org/p/jlfaXu6TZ8L >>>> >>>> Here's another way: https://play.golang.org/p/gEDef3LolAZ >>>> >>>> Matt >>>> >>>> On Friday, March 16, 2018 at 9:45:00 AM UTC-5, Sathish VJ wrote: >>>>> >>>>> All the examples I've seen use some kind of ticker to run various >>>>> cases of a select statement. But how does one run a long running task >>>>> that >>>>> is still cancelable? >>>>> >>>>> >>>>> In the example below the quit part is never reached. >>>>> >>>>> https://play.golang.org/p/PLGwrUvKaqn (it does not run properly on >>>>> play.golang.org). >>>>> >>>>> package main >>>>> >>>>> >>>>> import ( >>>>> "fmt" >>>>> "os" >>>>> "time" >>>>> ) >>>>> >>>>> >>>>> func f(quit chan bool) { >>>>> for { >>>>> select { >>>>> case <-time.After(0 * time.Second): >>>>> // start long running task immediately. >>>>> for { >>>>> time.Sleep(500 * time.Millisecond) >>>>> fmt.Printf(". ") >>>>> } >>>>> case <-quit: >>>>> fmt.Println("quit called") >>>>> //deallocate resources in other long running task and then >>>>> return from function. >>>>> os.Exit(0) // or return >>>>> } >>>>> } >>>>> } >>>>> >>>>> >>>>> func main() { >>>>> var quit chan bool >>>>> go f(quit) >>>>> >>>>> >>>>> println("quit sending ... ") >>>>> quit <- true >>>>> println("after quit sent") >>>>> >>>>> >>>>> var i chan int >>>>> <-i >>>>> } >>>>> >>>>> >>>>> -- > 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. > -- Michael T. Jones michael.jo...@gmail.com -- 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.