[HN Gopher] Updating the Go Memory Model
       ___________________________________________________________________
        
       Updating the Go Memory Model
        
       Author : gbrown_
       Score  : 193 points
       Date   : 2021-07-12 14:11 UTC (8 hours ago)
        
 (HTM) web link (research.swtch.com)
 (TXT) w3m dump (research.swtch.com)
        
       | judofyr wrote:
       | > In other words, Go encourages avoiding subtle bugs by avoiding
       | subtle code.
       | 
       | I find this philosophy strange when they talk about the
       | communication primitives. Channels are _extremely_ subtle in my
       | experience. A channel can be buffered or unbuffered, it can be
       | closed, and there's different behavior of read /write whether
       | it's in a select or not. And then you often end up with multiple
       | channels as well. Reviewing code with channels is often more
       | confusing than code that uses mutexes in my opinion.
        
         | tapirl wrote:
         | For some scenarios, the implementation code will be quite
         | complicated if only mutexes are used, whereas the
         | implementation will be quite simple by using channels.
         | 
         | I think your frustration comes from you wanted to use channels
         | for any scenarios. This is not recommended too.
         | https://github.com/golang/go/wiki/MutexOrChannel
        
           | karmakaze wrote:
           | It fails to mention the performance penalty with channels.
        
             | arghwhat wrote:
             | Because there is no generic penalty, and worrying ahead of
             | having a problem to solve sounds like a terminal case of
             | premature optimization.
        
               | brobinson wrote:
               | Not sure if it's the case nowadays, but Go channels were
               | historically around 4x slower than the equivalent mutex-
               | based code. This is a Big Deal(tm) if you're writing
               | performance sensitive code or in a hot loop.
               | 
               | https://www.jtolio.com/2016/03/go-channels-are-bad-and-
               | you-s...
               | 
               | https://www.datadoghq.com/blog/go-performance-tales/
        
               | karmakaze wrote:
               | I've had the opposite happen, where because of mindsets
               | like this, that it doesn't get properly discussed and
               | going in blind having to deal with it later in a case
               | where it was clear that it would matter.
        
             | tapirl wrote:
             | For some scenarios channels and mutexes are both suitable
             | for, mutextes are a bit more performant than channels. If
             | you do care about the performance penalty, just choose
             | mutexes. But the for scenarios mutextes are incapable of,
             | still using mutexes is surely not a good idea.
        
               | karmakaze wrote:
               | This would be a good thing to add to the reference.
        
               | jerf wrote:
               | Dunno. Tell people "channels are about 3 times slower
               | than mutexes" and watch them scramble the hell out of
               | their program trying to avoid channels, when the reality
               | is "in general, both of them are fast enough that they
               | will not be the bottleneck on your program".
               | 
               | In general (not just Go!), don't send tiny amounts of
               | work across any sort of internal boundary (goroutine,
               | thread, spark, green thread, whatever they call it). The
               | amount of work you send should significantly exceed the
               | costs of sending it. Follow this advice and the perf
               | differences between mutexes and channels will almost
               | certainly not be relevant; fail to follow it and neither
               | of them will be fast enough. I don't think there's
               | actually a lot of programs in the wild where the
               | performance difference between the two would be the make-
               | or-break difference. Non-zero, but not many. In practice
               | it's pretty clear developers will spend much more time
               | worrying over it than is justified.
               | 
               | (A common benchmark I see new programmers apply to any
               | language that claims to be good at multithreading is to
               | try to "parallelize" the act of adding a few million
               | integers together by sending individual integers or
               | addition problems out to threads, and wondering why it's
               | 10-100 times slower even though all my CPUs are at 100%,
               | so why does multithreading suck so hard in this language?
               | The problem is that no matter how cheap the send
               | operation is, it's going to be dozens or hundreds of
               | operations, whereas a single integer addition is
               | generally a single cycle, or possibly, amortized to less
               | than that depending on how good your compiler is. You
               | just need to be sure to send units of work larger than
               | the cost of sending them, which is generally not that
               | hard. This isn't just Go, I've seen this charge leveled
               | at Haskell, Erlang, and Python's multiprocessing too, and
               | I'm sure others have seen it in their own communities.
               | It's a very common mistake.)
        
               | stcredzero wrote:
               | _The amount of work you send should significantly exceed
               | the costs of sending it._
               | 
               | Jerf, you did it again! This is gold! Succinct and to the
               | point.
               | 
               | I'm going to think on this one.
        
               | karmakaze wrote:
               | This implies that you know the "costs of sending it."
        
         | silasdavis wrote:
         | Exactungly, Go's flavour of channels are full of subtle bugs. I
         | could count on N hands the number of times I've had to make a
         | channel to drain a channel.
        
         | whateveracct wrote:
         | > it can be closed
         | 
         | ..at most once!
        
         | tschellenbach wrote:
         | You can write Go for years without ever touching channels. I
         | agree though, they are extremely easy to make mistakes with.
        
         | parhamn wrote:
         | The main issue is channels are a very low level primitive (not
         | far from mutexes and atomics). When generics come out we there
         | will emerge a library that handles a lot of the common channel
         | idioms that people are implementing (perhaps somewhat
         | incorrectly).
         | 
         | Thanks like fanouts, multidispatch, sinks, chaining, simple
         | concurrency, etc will likely emerge in a lib in the future.
        
           | ra7 wrote:
           | This definitely the biggest thing I'm looking forward to in
           | Go's post-generics world. It's frustrating to write the same
           | concurrency abstractions like fan out, error handling etc for
           | every type and reason about channel behavior carefully every
           | single time.
        
         | AtNightWeCode wrote:
         | Is it not just basically an implicit actor model.
        
         | Thaxll wrote:
         | Channel are pipe if you want to use a mutex you have to re-
         | invent a queue with a mutex which is very different, a mutext
         | alone does not just replace a channel.
        
         | saghm wrote:
         | The thing that always got me was that closing a channel on one
         | end (I think the receiving end, but I can't remember exactly
         | since I haven't programmed Go for a while) causes an error if
         | the other side is open, but doing that on the other end (I
         | think the front) doesn't give that issue. Sure, there's an
         | explanation for that, but I feel like you could just as easily
         | make a justification for acting consistently on both ends.
        
           | owl57 wrote:
           | Don't know Go, but this way would be logical. "I've done
           | sending" and "I refuse to handle more messages" aren't that
           | symmetrical.
        
         | throwaway894345 wrote:
         | I agree with this. Channels have not lived up to their
         | promises, and I mostly avoid them in Go. They're handy
         | sometimes when you want to just block some goroutine (read from
         | a channel that is never written to) or for a few other use
         | cases, but mutexes are my go-to primitive.
        
         | skywhopper wrote:
         | He's stating the goal of the design, not an assertion that they
         | got everything right. Obviously there are lots of places where
         | the design fell short. But the philosophy behind the approach
         | to future design is unchanged. Channels were an attempt to add
         | something very new to the language and so it's not a surprise
         | that the design ended up being problematic. I'm sure Russ has
         | thoughts on that, too.
         | 
         | But the vast majority of Go code doesn't use channels. The fact
         | that lots of people have trouble with them doesn't change the
         | fact that the vast majority of Go code does follow this
         | principle.
        
         | ferdowsi wrote:
         | I believe the guidance addresses this.
         | 
         | > Programs that modify data being simultaneously accessed by
         | multiple goroutines must serialize such access.
         | 
         | > To serialize access, protect the data with channel operations
         | or other synchronization primitives such as those in the sync
         | and sync/atomic packages.
         | 
         | Go gives us `sync.WaitGroup` and `errgroup.Group` as
         | abstractions to manage concurrent operations. I always end up
         | guiding developers towards using these unless they absolutely
         | can't (like responding to OS signals requires interacting with
         | channels).
        
           | pkaye wrote:
           | For OS signals now they added NotifyContext() which uses
           | context instead of channels.
        
         | convolvatron wrote:
         | to me the overriding problem here is that selects need to be
         | carefully refactored when you add in other interactions. of
         | course.
         | 
         | but this means there are as you say subtle non-local
         | effects...and these kick in exactly when you don't want them -
         | when you are trying to thread in a new feature in an existing
         | codebase.
         | 
         | I prefer to just ignore channels. then that statement makes
         | sense :)
        
         | voidfunc wrote:
         | Hard agree about channels being a mess in Go. I avoid channels
         | as much as possible in favor of mutexes but the problem is the
         | community is so aligned towards using channels you get funny
         | looks when you solve problems with mutexes.
        
         | voidnullnil wrote:
         | Channels and an alt operator are much more powerful than locks
         | but eh I can't remember the edge cases in Go's channels so you
         | might be right,
        
         | eknkc wrote:
         | Whever I tried to use channels I regretted it at the end. I
         | believe the channels in Go are the worst part of the language.
         | 
         | I use mutexes.
        
           | nemo1618 wrote:
           | I agree (well, maybe not _the_ worst, but certainly _one of_
           | the worst), but in retrospect it 's hard to say whether Go
           | would have taken off the way it did, had it not pushed the
           | concurrency angle so hard. I think Go's focus on software
           | engineering (as well as its "stubbornness" in general) is far
           | more valuable than its concurrency features, but there's no
           | question the latter is more exciting and likely to drive
           | growth.
        
           | tapirl wrote:
           | How could it be? Programming with channels is not only
           | intuitive but fun for many cases:
           | https://go101.org/article/channel-use-cases.html
           | 
           | Yes, channels are not always best solutions for any case.
           | There are cases mutexes are more suitable. Just choose the
           | best solution for specified cases. Always sticking to one is
           | not a good attitude.
        
             | jeffreybolle wrote:
             | I agree with this.
             | 
             | We used to do a coding exercise at a previous job that was
             | really well suited to mutexes and whenever someone tried to
             | use channels they ended up with a much more complex
             | solution.
             | 
             | Equally, there are lots of cases which lend themselves to
             | channels really well. Waiting on multiple things at once is
             | hard to without channels and select for example.
        
         | laumars wrote:
         | It took me a loooong time to fully wrap my head around
         | channels. They're definitely not explicit and full of subtle
         | yet devastating bugs when used inappropriately (which is all to
         | easy to do too). Some of the boilerplate code around using them
         | is just ugly too.
         | 
         | That said, there are occasions when channels have proven
         | invaluable. I think the real issue is they were branded as a
         | killer feature but in reality their usefulness is a little more
         | niche than a mutex.
        
           | fpoling wrote:
           | Channels in Go lack priority support which made them unusable
           | to express some patterns.
           | 
           | Fortunately one can code a replacement for channels in Go
           | using mutexes and semaphores. And that exercise in turn
           | allows one to see that in many cases the channels are just a
           | bad model. Things like having a single polymorphic priority
           | message queue per go routine suits many cases better than
           | dealing with multiple channels.
           | 
           | And this has been known since nineties if not eighties when
           | Ada had to add mutexes after early designs based purely on
           | channels. So it is puzzling why Go made channels such a
           | central feature when the priority queue just works judging by
           | Erlang or many successful C/C++ libraries.
        
           | chakkepolja wrote:
           | Correct me if I am wrong, isn't a channel basically same as
           | blocking queues in Java? I know go but not much advanced.
        
             | clipradiowallet wrote:
             | I don't code Java, but I have the same sentiment about
             | channels in golang to queue's in python.
        
             | eitland wrote:
             | Seems like there are a few differences but nothing I
             | personally would leave normal indexing, kind of working
             | generics, Maven and a choice of 3 world class IDEs behind
             | for: https://stackoverflow.com/questions/10690403/go-
             | channel-vs-j...
             | 
             | To each their own though. I hear a lot of people prefer it.
        
           | tapirl wrote:
           | You should not blame Go channels for your inexperience of
           | using them.
        
           | HeyImAlex wrote:
           | They could have been a much more powerful primitive, but not
           | having generics means tons of boilerplate; all basic channel
           | patterns involve a lot of copy pasting.
        
             | jasonwatkinspdx wrote:
             | Once generics have been out there for a while and the
             | patterns of use in the community are clear/stable, I think
             | there's a decent chance they'll revisit channels to help
             | clean that up. It certainly is frustrating.
        
           | slaymaker1907 wrote:
           | The killer feature over a mutex, in my opinion, is that
           | mutexes encourage sharing by default which is terrible for
           | both program correctness and performance.
        
             | laumars wrote:
             | That depends massively on the kind of problems you're
             | trying to solve. I've written some software where channels
             | were the right tool, and others where mutexes were the
             | correct choice.
        
           | elithrar wrote:
           | > I think the real issue is they were branded as a killer
           | feature but in reality their usefulness is a little more
           | niche than a mutex.
           | 
           | Agree - and many people are surprised to learn both: a) how
           | fast mutex ops are in practice, and b) how channels use
           | mutexes under the covers.
        
         | naikrovek wrote:
         | it always shocks me when someone says that channels are not
         | immediately intuitive. they make so much sense to me, and they
         | did from the instant I read about them.
         | 
         | combined with goroutines, channels are absolutely one of the
         | best features of this language, to me.
         | 
         | async & await, on the other hand, still confuse me and trip me
         | up today, even after using them for years, because of very
         | weird implementation minutia and edge cases that I always seem
         | to stumble into.
         | 
         | I wish async and await would disappear from the face of the
         | earth and I wish coroutines and channels were in every language
         | I use.
        
           | throwaway894345 wrote:
           | I've been using Go avidly since 2012. I still run into
           | deadlocks and panics (writing to a closed channel) when I try
           | to use channels beyond the simplest use cases. Error handling
           | in a parallel context is also hairy with channels. I've
           | learned to minimize my use of channels and prefer mutexes as
           | my default concurrency primitive.
           | 
           | It really only means that CSP looks nice on paper but (at
           | least as implemented in Go) doesn't work out well in
           | practice. There's lots to like about the language even if the
           | CSP theory didn't pan out.
        
             | cp9 wrote:
             | same, I like go and I like channels in theory but they are
             | too primitive in practice. I am much more likely to use
             | waitgroups and errgroups than anything with raw channels
        
             | itake wrote:
             | My understanding is that if you follow the rule of using
             | generator functions (creator of the channel is responsible
             | for writing and the closing) it's impossible to write to a
             | closed channel.
             | 
             | When would that pattern not be useful?
        
               | gilgad13 wrote:
               | The rule that the writer should be responsible for
               | closing a channel is a good one to keep in mind, but it
               | is often the case that you want to launch an
               | indeterminate number of generators and collect the
               | results from all of them. For example, you may want to
               | make one connection to each server in the config, or to
               | process each file in a directory in parallel. Because the
               | arms of a `select{}` are determined at compile time, it
               | cannot be used to select over the variable generator-
               | owned channels, and you have four more difficult options:
               | 
               | * Use `reflect.Value.Select`[1]. Having to reach for
               | reflect feels ugly for such a common case, and the
               | performance of the reflect-select is much lower than the
               | native select.
               | 
               | * Create a single channel owned by the reader, pass it to
               | each writer, and arrange for this channel to be closed
               | when the final writer exits, through a waitgroup. There
               | is an example under "Parallel digestion" in the Go
               | Concurrency Patterns blog post[2]. Note the little
               | details to get right. We must launch a separate goroutine
               | to monitor the waitgroup / channel closure. If we
               | accidentally do it in-line at the wrong level, everything
               | will work fine if the total number of items written to
               | `c` is less than `c`'s capacity, but will hang once a
               | worker becomes blocked on `c`. Additionally, the
               | waitgroup is threaded directly into the writers, which
               | may be more difficult if those are implemented in some
               | other generic package.
               | 
               | * Wrap the above pattern up into a `merge` function, such
               | as the one under "Fan-in, Fan-out" in the Go Concurrency
               | Patterns post[2]. The lack of generics means we will have
               | to copy-paste this function everywhere we want to use it.
               | Additionally, this launches a goroutine for every channel
               | being watched, which strikes people as "expensive" for
               | such a simple operation.
               | 
               | * We can construct a function that takes two channels and
               | launches a goroutine that selects between the two and
               | writes to a merged output channel. By constructing a tree
               | of these we can merge an arbitrary number of channels.
               | This is really just an optimization of the above.
               | 
               | None of these options are particularly intuitive. Too
               | often I've instead seen developers create a single
               | channel owned by the reader and either:
               | 
               | * Assume it is never closed and the reader doesn't
               | terminate until the application does
               | 
               | * Rely on some external mechanism to know when to stop
               | reading. If the reader can stop reading without
               | confirming that the writers have stopped writing, this
               | can lead to the writers becoming blocked on sending into
               | this channel, which may prevent them from performing
               | necessary cleanup actions (signaling `.Done()` on a
               | waitgroup, for instance) that cause hangs in other areas.
               | 
               | * Thread a cancellation ctx through every reader and
               | writer. This ensures that nothing hangs, but can result
               | in messages that are sitting in the the channels being
               | dropped. If other areas of code have an assumption like,
               | "every accepted request will receive a response", this
               | can break that.
               | 
               | In addition, many developers have a gut instinct to add
               | some amount of buffering to their channels, which usually
               | results in these backpressure / channel issues being
               | papered over during low-load unit tests, only to rear
               | their head during higher load integration tests or in
               | production, when the debugging story is much more
               | difficult.
               | 
               | [1]: https://pkg.go.dev/reflect#Select
               | 
               | [2]: https://blog.golang.org/pipelines
        
           | beltsazar wrote:
           | I too thought Go's channels are intuitive.. until I found
           | Channel Axioms: https://dave.cheney.net/2014/03/19/channel-
           | axioms
           | 
           | I keep going back to that page whenever I need it, because I
           | could never remember what the axioms are.
        
           | tene wrote:
           | Channels as a high-level abstraction are pretty simple, but
           | API and implementation details matter.
           | 
           | Go's implementation of channels are very simple to use for
           | some very simple use-cases, but Go's API choices mean there
           | are a lot of subtle, non-obvious details you need to learn
           | and keep in mind to do anything nontrivial.
           | 
           | I really like https://medium.com/justforfunc/why-are-there-
           | nil-channels-in... as an example. Reading from two channels
           | safely should be a simple task, but just doing the intuitive
           | thing will look like it works for many uses until it starts
           | fabricating zero values, or blocks forever, or spins the CPU
           | at 100% doing no work.
           | 
           | I really love channels, but I really hate working with Go's
           | channels.
           | 
           | The channel API in that other language well-known for its
           | good concurrency support is much simpler to learn and use for
           | me, without as many subtle sharp edges, but that's possibly a
           | bit off-topic, so I've removed a detailed comparison.
        
             | tapirl wrote:
             | What you described are all well defined in the docs.
        
         | ryanschneider wrote:
         | The thing that has bitten me the most is somewhat related: the
         | lack of any uniform "deep copy" or immutability support. Sure I
         | can make a `chan Foo` but if Foo is a struct with an embedded
         | pointer in it you just aliased a pointer you probably didn't
         | mean to.
         | 
         | If there was supported for optional value types rather than
         | using pointers that would help too.
        
         | axaxs wrote:
         | I find channels massively overcomplicate many things. Cool in
         | theory, not so useful in practice.
         | 
         | Anymore, I only use them as a semaphore to keep less than X
         | routines running at once.
        
         | azth wrote:
         | Not to mention that there is no way to ensure immutability when
         | passing messages in channels, and the language doesn't help you
         | there. This is a recipe for race conditions.
         | 
         | Furthermore, you can easily have hierarchies of "goroutines",
         | everything has to be painstakingly built from scratch and is
         | very error prone.
         | 
         | > Another aspect of Go being a useful programming environment
         | is having well-defined semantics for the most common
         | programming mistakes, ... Quoting Tony Hoare
         | 
         | One of the most common programming mistakes of all, dubbed by
         | Hoare himself as the "billion dollar mistake": null pointers,
         | yet it was put into go without any consideration.
        
           | throwaway894345 wrote:
           | > One of the most common programming mistakes of all, dubbed
           | by Hoare himself as the "billion dollar mistake": null
           | pointers, yet it was put into go without any consideration.
           | 
           | I get the distinct impression that people who invoke this
           | boilerplate Hoare quote argument don't understand that null
           | pointer bugs are "a billion dollar mistake" because nullable
           | pointers have been idiomatic in every major programming
           | language for the last 40+ years, _not because they are
           | individually particularly expensive_.
           | 
           | Yeah, Rust-like enums would probably be strictly better than
           | null pointers, but people talk about null pointers like they
           | are going to doom your project when in reality they're mere
           | papercuts--a small subset of all type errors--and there are
           | _whole projects that are written in fully dynamically typed
           | languages!_ Indeed, there are worse problems than type errors
           | in a language--you could have an impoverished ecosystem, poor
           | tools (especially build tools), or abysmal performance, and
           | many of the most popular programming languages have two of
           | the three _and_ null pointers. Go is fortunate to have only
           | null pointers (papercuts) working against it.
        
             | voidnullnil wrote:
             | I've never bothered to look up the hoare thing, but null as
             | a member of all types increases the amount of time spent
             | studying any given API (and of course, there are lots of
             | null pointer bugs from people who didn't bother to study
             | the API sufficiently).
        
               | throwaway894345 wrote:
               | Yeah, as previously mentioned, I fully agree that
               | nullable pointers kind of suck and exhaustive pattern
               | matching on enums is strictly better. I'm taking issue
               | narrowly with abusing the "billion dollar mistake" quote
               | to exaggerate the severity of the problem.
        
               | voidnullnil wrote:
               | It's a big problem in Java at least. In Go you have value
               | types by default and they try to make zero a meaningful
               | value.
        
               | throwaway894345 wrote:
               | It's not a big problem when you look at the problems that
               | plague even the most popular programming languages. At
               | this point I can only refer you to my previous posts
               | because I'm repeating myself. :)
        
         | richardwhiuk wrote:
         | I agree - I find Go's error handling encourages subtle bugs.
        
           | AlexCoventry wrote:
           | Can you give an example?
        
           | Philip-J-Fry wrote:
           | The only place I would agree with you is when someone
           | accidentally shadows an `err` variable. Although, I'd
           | probably attribute this more to shadowing than the error
           | handling itself.
           | 
           | Other than that, I can't think of any way the error handling
           | subtly introduces bugs.
        
             | randomdata wrote:
             | The onus is on the programmer to understand what errors
             | might be returned, which isn't always easily gleaned,
             | especially when calling a function that calls other
             | functions that call other functions, each pushing the error
             | up the stack. The error interface does not provide much
             | information on its own.
             | 
             | The mistake of the programmer missing an error that should
             | be handled in a particular situation could be considered a
             | subtle bug, I suppose. This is different to languages that
             | have compiler-enforced error handling, where if you forget
             | to handle a certain case the code won't compile.
        
               | jerf wrote:
               | None of that is unique to Go, and, indeed, the question
               | of "what errors may be returned" in general is not solved
               | in any language that I know of satisfactorily, especially
               | once you include any form of polymorphism. Compiler-
               | enforced error handling in general only works on the
               | direct possible errors from the function you just called,
               | not the transitive closure of everything that could have
               | happened, again, especially once you include any sort of
               | polymorphism, meaning that you don't even know at the
               | time you're writing the error handler what the
               | possibilities are, and depending on the language, it may
               | not even be possible to statically know. Don't mistake
               | "option" or "sum types" for a solution to this problem;
               | they aren't.
               | 
               | (The only language I know where this is nominally
               | "solved" is Java if you confine yourself to its checked
               | exceptions, but nobody considers that solution
               | "satisfactory", and indeed can be taken as a rough-and-
               | ready proof that there may not even _be_ a  "static"
               | solution to this problem that is usable. I'm beginning to
               | think of it as "Errors don't compose, because they
               | contain every possible pathology that would prevent
               | values from composing." At this point, based on the
               | consistent failures trying to create composable error
               | solutions despite substantial effort poured into it
               | across multiple languages, I am going to operate under
               | the assumption there is no such solution until someone
               | produces one.)
        
           | voidnullnil wrote:
           | Go's errors have basically all the problems of exception
           | handling in previous generation languages (and a few novel
           | ones).
           | 
           | This explains it (tl;dr nobody bothers to formally define
           | what errors they return, and the blindly propagate such
           | unspecified errors, and it leads to ambiguity and
           | unintentional program behavior)
           | 
           | https://www.lainchan.org/%CE%BB/src/1612397614615.jpg
           | 
           | https://www.lainchan.org/%CE%BB/src/1612397701473.jpg
           | 
           | https://www.lainchan.org/%CE%BB/src/1612398029019.jpg
        
       | karmakaze wrote:
       | > To serialize access, protect the data with channel operations
       | or other synchronization primitives such as those in the sync and
       | sync/atomic packages.
       | 
       | > If you must read the rest of this document to understand the
       | behavior of your program, you are being too clever. Don't be
       | clever.
       | 
       | I'm surprised that they list sync/atomic as a thing to use. As I
       | recall its behaviour isn't even defined--I followed a long
       | mailing list thread trying to find out that ended with 'these are
       | the behaviours we know we want but it's too complicated to
       | document so let's just keep this to ourselves'.
        
         | rsc wrote:
         | Keep reading?
        
           | karmakaze wrote:
           | Yes, that was about the original Go documentation. The
           | suggestion still doesn't mention that the difficultly with
           | sync/atomic isn't the memory model per-se but rather the lack
           | of yielding to another goroutine.
        
         | dragontamer wrote:
         | 1. The compiler's #1 job is removing code (aka optimizing): it
         | does this by rearranging code, merging code together, and other
         | forms of optimizations. That is to say: "i=0; i++; i++" wants
         | to optimize to "i=2;", but in a multithreaded context, it means
         | that you'll "never" see "i==0", or "i==1". Turns out that
         | "losing" these states can be an issue if you're building
         | multithreaded primitives (lock-free code, atomics, etc. etc.)
         | 
         | 2. The CPU and L1 cache also "effectively moves" code in
         | relaxed-architectures like ARM / Power9 / DEC Alpha. So it
         | turns out that #1 is true "even if the compiler wasn't
         | involved".
         | 
         | 3. Because of #2, might as well fix the compiler / CPU / L1
         | with the same set of primitives (the "memory model") that
         | defines orderings.
         | 
         | 4. Turns out that a large, but minority, number of programmers
         | want to experiment with low-level multithreading primitives:
         | researchers, speed-demon professionals, and others do want to
         | go at "full speed" even at the cost of great complexity.
         | Unifying the promises of the compiler + CPU + L1 cache all
         | together in a SINGULAR model helps dramatically (that way: the
         | programmer only fights the compiler. The compiler has to
         | "translate" the CPU / L1 cache rearrangements into low-level
         | barriers as appropriate)
         | 
         | -------
         | 
         | It turns out that the biggest source of speed improvements
         | exists in this realm of "rearranging code". That's why CPUs do
         | out-of-order execution. That's why CPUs (like ARM or POWER9)
         | want more relaxed execution, to allow the CPU to rearrange more
         | code in more situations. That's why L1 cache exists (and
         | similar: L1 wants to rearrange reads/writes even more
         | aggressively for even faster operations).
         | 
         | If you make a memory barrier (nominally preventing the L1 cache
         | from rearranging code), you SHOULD have the CPU and compiler
         | respect the barrier as well. After all, if the programmer says
         | "this should not be rearranged", then that probably applies to
         | compiler, CPU, and L1 cache all the same.
        
           | nyanpasu64 wrote:
           | Ten years on, was the C++11 memory model (which I've used) a
           | success? Compared to the Linux kernel memory model (which I
           | haven't used)? I heard compilers can't remove dead reads
           | because they can synchronize in rare situations, and
           | sequential consistency was defined in a broken way and later
           | fixed in a standards revision, and memory_order_consume is
           | impossible to correctly implement in a way that's actually
           | more optimized than memory_order_acquire, and the C++ memory
           | model doesn't translate well to GPUs.
           | 
           | Is this better than the state of affairs prior to
           | standardized atomics (which I haven't experienced)? Is it
           | better than Go "defining enough of a memory model to guide
           | programmers and compiler writers" (which I haven't used)? Or
           | informally defining a set of use patterns, and writing
           | optimizations around those use patterns rather than a formal
           | model for what code and what optimizations are permitted
           | (resulting in optimization steps that are only incorrect in
           | combination, like global value numbering causing
           | miscompilations[1][2])?
           | 
           | [1]: https://github.com/rust-lang/rust/issues/45839
           | 
           | [2]: https://bugs.llvm.org/show_bug.cgi?id=35229
           | 
           | EDIT:
           | 
           | > the critical detail about [relaxed/unsynchronized]
           | operations is not the memory ordering of the operations
           | themselves but the fact that they have no effect on the
           | synchronization of the rest of the program.
           | 
           | Many people wish C++ memory_order_relaxed had no effect on
           | synchronization and could be optimized like a normal access.
           | It's not. https://internals.rust-lang.org/t/unordered-as-a-
           | solution-to...
        
             | dragontamer wrote:
             | Acquire-Release seems to be an outstanding success. ARMv8
             | added new assembly language statements to support it... as
             | did NVidia GPUs (clearly CUDA / PTXis moving towards
             | Acquire-release semantics), compilers from all around, etc.
             | etc. So many systems have implemented acquire-release that
             | I'm certain it will be relevant into the future.
             | 
             | Consume-release is a failure, but it seems like it was
             | "expected" to be a failure to some degree. Consume-release
             | was apparently the model that ARMv7 / Older-POWER assembly
             | designers were going for, but it turned out to be far too
             | complicated to think about. No compiler seems to be using
             | consume-release anywhere (instead turning consume into
             | Acquire).
             | 
             | From my understanding, the Linux-kernel operations could be
             | consume-release, but only if the compilers fully understood
             | the implications. (But no one seems to fully understand
             | them). Maybe a future standard will fix consume-release,
             | but best to ignore it for now.
             | 
             | Anyway, ARMv8 and POWER9 have changed their assembly
             | language to include Acq/Release level semantics.
             | 
             | Fully relaxed is... not a model at all and does the job
             | spectacularly! Some people don't want any ordering what so
             | ever, lol.
             | 
             | Seq-cst is basically Java's model and it works for those
             | who don't care about optimizations (it will necessarily be
             | slower than Acquire/release. But there's a few cases where
             | acquire/release is a trap and Seq-cst is necessary). It
             | doesn't work on GPUs though as GPUs don't have snooping
             | caches / coherence IIRC. So the strongest you can get in
             | CUDA-land is Acq-release.
        
               | [deleted]
        
               | nyanpasu64 wrote:
               | > Fully relaxed is... not a model at all and does the job
               | spectacularly! Some people don't want any ordering what
               | so ever, lol.
               | 
               | Many people wish C++ memory_order_relaxed had no effect
               | on synchronization and could be optimized like a normal
               | access. It's not. https://internals.rust-
               | lang.org/t/unordered-as-a-solution-to...
        
             | adwn wrote:
             | > _Ten years on, was the C++11 memory model (which I 've
             | used) a success?_
             | 
             | Concurrent/parallel programming in C and C++ before the
             | memory model was an absolute shitshow. You could either
             | 
             | a) scream "YOLO lol" and resort to abusing _volatile_ (and
             | secretly hoping that no one will ever actually execute your
             | code on a CPU with more than one core [1]), or
             | 
             | b) carefully construct synchronization routines in assembly
             | and try to make sure that the single compiler you support
             | doesn't screw you over in its effort to make your program
             | run super-fast (and slightly wrong) [2], or
             | 
             | c) use a library which handles b) for you.
             | 
             | [1] FreeRTOS does this, and it only supports a single core.
             | 
             | [2] Linux did (does?) this.
        
               | nyanpasu64 wrote:
               | Yikes, that is scary. Perhaps Go was more of a clear
               | improvement than dealing with this "shitshow", and C++
               | has become more usable for concurrent/parallel code in
               | the years since.
               | 
               | I still find data races (sometimes crashes) in the wild
               | on a regular basis. For example, RSS Guard accesses
               | shared data unsynchronized when syncing settings from a
               | server, so performing two types of syncs at once on 2
               | different threads will crash when they reallocate the
               | same vector. Qt Creator intermittently crashes (or at
               | least used to) in some tricky CMake handling code with
               | multithreaded copy-on-write string lists. And I see apps
               | now and then that perform unsynchronized writes to memory
               | concurrently read in another thread, and it _usually_
               | doesn 't misbehave.
        
       | none_to_remain wrote:
       | I just appreciate that the Go Memory Model intro basically says
       | "if you have to read this, you are probably wrong" and felt free
       | to skip the rest.
        
       | kubb wrote:
       | Am I the only one who finds Go concurrency model to be extremely
       | bug prone and difficult to work with? Writing an efficient
       | pipeline of goroutines connected with channels that doesn't
       | deadlock is a verbose and subtle mess with a whole bunch of code
       | duplication.
       | 
       | For a lot of the things that I want to achieve, functional
       | abstractions like parallel map and reduce would be much more
       | understandable, and easier to use.
       | 
       | That whole talk about philosophy distracts from the actual tools
       | that Go gives you to manage concurrency which in my opinion can
       | be improved a lot.
        
         | Thaxll wrote:
         | "Writing an efficient pipeline of goroutines connected with
         | channels that doesn't deadlock is a verbose and subtle mess
         | with a whole bunch of code duplication."
         | 
         | It takes 30 lines of code and it's very easy.
        
           | 1ris wrote:
           | This _has_ to be depended on the pipeline you want to have.
        
             | Thaxll wrote:
             | It depends but saying it's very complicated and it's easy
             | to deadlock is pure bs.
             | 
             | Where is the complexity in Go to have a pool of workers
             | looking for jobs on a channel exactly?
        
               | ackfoobar wrote:
               | > Where is the complexity in Go to have a pool of workers
               | looking for jobs on a channel exactly?
               | 
               | This is just one kind of pipeline. If this is all you
               | have done, you may have the impression that concurrency
               | (in Go) is easy.
               | 
               | BTW, this is worth reading:
               | 
               | Understanding Real-World Concurrency Bugs in Go
               | https://songlh.github.io/paper/go-study.pdf
        
               | Thaxll wrote:
               | I know that paper and it's indeed a good one but I still
               | don't agree with OP that "Go concurrency model to be
               | extremely bug prone".
        
               | BobbyJo wrote:
               | I agree. Pointing out the difficulty of concurrency as a
               | general premise doesn't invalidate Go's model. For the
               | use cases it was designed for (asynchronous services)
               | it's model is probably the best. For use cases it wasn't
               | designed for, it probably sucks.
        
         | bjt wrote:
         | I think different people read "Go concurrency model" to mean
         | different things.
         | 
         | If we're talking about green threads (goroutines), I'm a huge
         | fan. Having had a lot of success using Gevent in the Python
         | world, it was great coming over to the Go world where that
         | pattern was baked into the language.
         | 
         | If we're talking about channels, I'm really not a fan. Not
         | nearly as easy to use as the equivalent Queue library over in
         | Python, despite being baked into the language. My experience
         | aligns with this post: https://www.jtolio.com/2016/03/go-
         | channels-are-bad-and-you-s... Almost every time someone's
         | suggested a Go channel, I've found it simpler to use a mutex or
         | waitgroup.
        
       | candiddevmike wrote:
       | Somewhat related, how are other gophers handling dynamic
       | configuration in their go apps? Such as if the app is tied to
       | consul or vault to get some config value. How do you handle the
       | propagation of config changes cleanly, especially if it impacts
       | something "big" like your DB pool? Do you have mutexes everywhere
       | you read from the config pointer you pass around?
       | 
       | I noodled on this a while ago and figured it was easier to just
       | restart the app on config changes than try to change things on
       | the fly =D
        
         | Thaxll wrote:
         | Why would you send pointer and not just a copy of a
         | configuration? Configuration are usually simple things like
         | url, threadpool size, strings etc ... there is no problem
         | passing a copy of that.
         | 
         | Edit: not sure why I'm downvoted, I also think that reloading
         | configuration is a bad idea anyway so you should pass immutable
         | config.
        
         | Philip-J-Fry wrote:
         | I don't use any of them services, but any config that has
         | multiple readers and a single writer (who updates it when it
         | changes) can just be protected by a sync.RWMutex.
         | 
         | The easiest thing to do would be to add a RWMutex to your
         | Config struct. Then have public accessor methods which RLock
         | the mutex while retrieving a field value.
         | 
         | Then when you want to update the config have something like a
         | `Set(c Config)` method which allows you to overwrite the config
         | stored in pointer. This Locks the RWMutex for writing of
         | course.
        
         | kyrra wrote:
         | atomic.Value can work pretty well as well, their example docs
         | show how to do it as well:
         | https://golang.org/pkg/sync/atomic/#example_Value_config
         | 
         | DBPool gets a bit more interesting. In Java world you can you
         | providers that give you access to something, which is what
         | middleware tends to do for Go. If you can have your middleware
         | be able to swap the pool config its loading from, that should
         | work just fine. atomic.Value make work here as well.
        
         | maximente wrote:
         | viper, which nicely handles env vars, also can watch for config
         | changes: https://github.com/spf13/viper#watching-and-re-
         | reading-confi...
         | 
         | if you're rotating creds or need to open/close the DB, this
         | will typically just add another select case to your main
         | method, where you also block on e.g. signal catching to cleanly
         | shutdown the app
        
           | snupples wrote:
           | Be aware that viper isn't thread safe. You'd still have to
           | copy config by value and then put a mutex on that, then
           | handle reads and writes separately.
           | 
           | https://github.com/spf13/viper#is-it-safe-to-concurrently-
           | re...
        
         | jy3 wrote:
         | Just implement methods in your config struct in such a way that
         | they are safe for concurrent access?! Using a mutex for
         | example.
         | 
         | Why would you need it to lock mutexes everywhere?
        
         | spyspy wrote:
         | Usually http handlers or a database functions are pointer
         | methods on some larger Service or DB struct type. If the config
         | changes, you'd have a mutex within that struct and a method
         | that safely modifies its state by locking and unlocking the
         | mutex.
        
         | glenjamin wrote:
         | > I noodled on this a while ago and figured it was easier to
         | just restart the app on config changes than try to change
         | things on the fly =D
         | 
         | I think this is generally the best approach, as it also applies
         | some pressure for your application to be able to quickly
         | shutdown and restart without affecting users - which is a
         | desirable property to have for many other reasons too.
        
       | knorker wrote:
       | Good example of the Go patronizing tone. "We're not C++, with its
       | garbage UB. Oh, but we have memory corruption abilities from data
       | races".
       | 
       | Oh, right.
       | 
       | Yes, i know C++ has more interesting failures for invalid code,
       | but it's pretty misrepresenting to say "Go sits somewhere in the
       | middle", as if you can be a little bit pregnant.
       | 
       | Other than that, yeah seems good, but not earth shattering
       | incremental improvement.
        
         | throwaway894345 wrote:
         | Go's UB is in a rare edge case. I've been writing Go for a
         | decade and I've never run into it. No doubt some have, but this
         | is in stark contrast to C++ where UB is all over the place. The
         | salient detail is that UB isn't a binary like you propose. It's
         | not like "am I pregnant", it's more like "how much alcohol have
         | I consumed"?
        
       | twotwotwo wrote:
       | > Note that this means that races on multiword data structures
       | can lead to inconsistent values not corresponding to a single
       | write. When the values depend on the consistency of internal
       | (pointer, length) or (pointer, type) pairs, as is the case for
       | interface values, maps, slices, and strings in most Go
       | implementations, such races can in turn lead to arbitrary memory
       | corruption.
       | 
       | That second sentence is one of the painful things about the Go
       | memory model--Go is type and memory safe if you avoid explicitly
       | unsafe things, _except_ for data races on those structures.
       | 
       | I wonder what you can practically do to mitigate that at this
       | point; doesn't seem like you go back and change things to be like
       | Java. Can there be some best-effort detection for races in those
       | specific places that's fast enough to run in prod (like I think
       | they did for maps)? I also recall some Go mailing-list post I
       | can't find suggesting that an SSE 128-bit move, while not
       | guaranteed not to tear, seemed to tear only rarely in their tests
       | on the chips of the time; is that true and is there anything
       | there? I imagine fully preventing the problem with atomic
       | reads/writes of these pairs from the heap is a clear no-go
       | performance-wise, and might involve ABI-changing alignment
       | guarantees.
       | 
       | [Edit: SSE2 behavior is discussed here
       | https://stackoverflow.com/questions/7646018/sse-instructions...
       | which is mentioned in a comment at
       | https://research.swtch.com/gorace which seemed to inspire it.
       | Chance of a race seemed to depend on CPU model at the time, bad
       | on the old Core Duo, better on some server chips another answerer
       | tried. Good on my laptop FWIW!]
       | 
       | Probably hard for anything to happen here because _any_
       | performance regression is hard to swallow, the race detector is
       | close enough for many, and anything you do won 't be a 100%
       | solution anyway. Which is a bummer.
        
         | throwaway34241 wrote:
         | > I wonder what you can practically do to mitigate that at this
         | point; doesn't seem like you go back and change things to be
         | like Java.
         | 
         | Wouldn't there be an obvious trade off there, requiring an
         | extra heap allocation and indirection every time a slice is
         | used in a data structure? I've always thought of Go as lower-
         | level than Java (since it has explicit pointers, more control
         | over memory layout, etc), which I think makes it a more useful
         | language overall even if not the best fit for every application
         | (since if you _do_ want Java, it already exists and is mature).
         | 
         | In other words, I think both Java's and Go's decisions make
         | sense here.
        
         | TheDong wrote:
         | > Go is type and memory safe if you avoid explicitly unsafe
         | things, except for data races on those structures
         | 
         | Note that this isn't actually a real exception. maps are
         | implemented in the go stdlib using "unsafe", in addition to
         | compiler magic for the generics part.
         | 
         | I believe that it holds that if you don't use "unsafe" then
         | your program is memory and type safe, it just happens that the
         | go stdlib and runtime use "unsafe", so you can't safely use
         | them either.
         | 
         | I get that this is a distinction without a difference.
        
           | throwaway894345 wrote:
           | Data races aren't limited to maps, so I'm not sure it's even
           | a distinction at all :)
        
         | ngrilly wrote:
         | > doesn't seem like you go back and change things to be like
         | Java
         | 
         | Java is like Go in that regard. That's why you have to use
         | ConcurrentHashMap instead of HashMap for instance.
        
           | twotwotwo wrote:
           | Java's trick is maintaining memory/type safety in the
           | presence of races. You might crash, and your application
           | might not see the values it expects, but you never get a
           | buffer-overflow-like situation where Java's internal
           | structures might get corrupted in arbitrary ways, for
           | example.
        
             | ngrilly wrote:
             | I see your point now. Yes, Java won't segfault, unlike Go
             | on slices/maps/interface values. But even without
             | segfaults, undefined behaviors due to race conditions can
             | still be pretty in Java (causing infinite loops, memory
             | leaks, etc.). Maybe it's better to let the program crash
             | with a segfault.
             | 
             | https://stackoverflow.com/questions/51102644/what-the-
             | worst-...
        
               | gpderetta wrote:
               | No it is not better. Java will preserve the integrity of
               | the runtime. Still memory safety issues in go due to
               | races are probably very hard to exploit so it might not
               | make a huge difference in practice.
        
               | ngrilly wrote:
               | Yes, the JVM will keep executing the program, but I'm
               | still not convinced this is strictly better than
               | segfaulting if a thread is stuck in an infinite loop
               | using CPU for nothing, and another is leaking memory.
        
               | gpderetta wrote:
               | It is better than invoking random code because the
               | runtime mistakes some data for a vtable pointer.
        
               | ngrilly wrote:
               | I agree that a data race on an interface value in Go, is
               | worse than an infinite loop or a memory leak, because it
               | can lead to calling the wrong method on a value, which is
               | hard to debug.
        
               | Diggsey wrote:
               | "Undefined behaviour" has a specific technical meaning.
               | In Go, data races can result in UB. In Java, they can't.
               | 
               | Java may be non-deterministic, or exhibit _unintended_
               | behaviour in response to a race condition. UB is strictly
               | worse because it could do either of those things... or
               | indeed anything else, segfaulting being the least of your
               | concerns.
        
               | ngrilly wrote:
               | This is the "technical meaning" of "undefined behaviour"
               | according to Wikipedia:
               | 
               | > Undefined behavior (UB) is the result of executing a
               | program whose behavior is prescribed to be unpredictable,
               | in the language specification to which the computer code
               | adheres.
               | 
               | Can we predict the behavior of a Java program with a data
               | race?
        
               | anderskaseorg wrote:
               | A section of Java code with a data race can't break
               | anything that the same section of code couldn't have been
               | rewritten to intentionally break without using a data
               | race. For example: it might throw an exception, loop
               | infinitely, or return valid values that the programmer
               | didn't expect; but it cannot corrupt the internal state
               | of the JVM, violate type-safety, fiddle with private
               | fields, or access data that isn't reachable from its own
               | variables. Data races cannot be used to violate a
               | security barrier between mutually untrusting sections of
               | Java code.
        
               | UncleMeat wrote:
               | This depends on what "unpredictable" means. Do I know
               | precisely what this java program will compute? No. But I
               | do know some things that it won't do. Its behavior is not
               | completely unbounded.
        
           | [deleted]
        
           | ibraheemdev wrote:
           | Java will throw a ConcurrentModificationException. Go can
           | cause undefined behavior.
        
             | papercrane wrote:
             | Java might throw a ConcurrentModificationException. The
             | docs are quite clear that it's not guaranteed and best-
             | effort only.
        
             | ngrilly wrote:
             | This is not guaranteed at all:
             | 
             | > Note that the fail-fast behavior of an iterator cannot be
             | guaranteed as it is, generally speaking, impossible to make
             | any hard guarantees in the presence of unsynchronized
             | concurrent modification. Fail-fast iterators throw
             | ConcurrentModificationException on a best-effort basis.
             | Therefore, it would be wrong to write a program that
             | depended on this exception for its correctness: the fail-
             | fast behavior of iterators should be used only to detect
             | bugs.
             | 
             | Source: https://docs.oracle.com/javase/8/docs/api/java/util
             | /Vector.h...
             | 
             | This is conceptually similar to the race detector in Go,
             | which is also a debugging tool, but without strict
             | guarantees.
        
         | pierrebai wrote:
         | I came here to quote and comments on this paragraph. The poke
         | at C/C++ undefined behaviour, claiming Go has no such thing.
         | Except this sentence is exactly undefined behaviour: after the
         | data race, due to possible corruption, anything could happen.
         | 
         | There is also equivalently smugness about the language in
         | general. It is one thing to say there are no undefined
         | behaviour related to the memory model, but given a compiler
         | optimizer, you need to do hard analysis that there cannot be
         | once optimized, with all operations re-ordering that can
         | happen.
         | 
         | The simplicity of the Go memory model, which is vague on
         | purpose, is taken as making it safe. Is it? It mostly mean it
         | is vague, which provide a convenient cover to hide under.
         | 
         | Edit: toward the end, the author talks about adding
         | documentation about compiler optimizations that would be
         | explicitly forbidden. So some of my earlier comments would be
         | addressed.
        
           | Jabbles wrote:
           | _you need to do hard analysis that there cannot be once
           | optimized, with all operations re-ordering that can happen_
           | 
           | The previous post demonstrated that this is extremely
           | difficult, beyond the ability of cutting edge research for
           | mainstream languages.
           | 
           | https://research.swtch.com/plmm
        
             | Jweb_Guru wrote:
             | I'm a bit tired of reading motivated blog posts like this
             | that bring up enough related work that you know they _know_
             | solutions exist to the problems they 're bringing up, but
             | for some reason felt it was not necessary to bring up those
             | solutions. This line in particular is plain false:
             | 
             | > None of the languages have found a way to formally
             | disallow paradoxes like out-of-thin-air values, but all
             | informally disallow them.
             | 
             | The post neglects to mention the Promising Semantics paper
             | of 2017 that resolves the out of thin air problem to (as
             | far as I know) everyone's satisfaction, despite pointing
             | out the previous work that brought up the problem. Similar
             | things are true for ARM's memory model, etc.--this is all
             | stuff that's been mostly resolved within the last few years
             | as proof techniques have caught up with compilers and the
             | hardware. Ironically, the thing that's been hardest to
             | formalize by far in a useful way (outside of C++ consume)
             | is--surprise, surprise--sequentially consistent fences!
             | 
             | It also handwaves away as some unimportant point the reason
             | why compilers provide things like relaxed accesses--it's
             | not just (or even primarily) about the hardware, but about
             | enabling useful compiler optimizations. Even if all
             | hardware switched to sequentially consistent semantics,
             | don't expect languages that aim for top performance to
             | abandon weak memory. And personally, I think it's ironic
             | that at a time when even Intel is struggling to maintain
             | coherent caches and TSO, and modern GPU APIs don't provide
             | sequential consistency at all, people are trying to act
             | like hardware vendors will realize "the error of their
             | ways" and go back to seqcst.
        
           | lamontcg wrote:
           | > There is also equivalently smugness about the language in
           | general.
           | 
           | This really turns me off about Go. And it seems to attract
           | "tech-splainers" that defend the language choices that are
           | made by talking down to people.
        
             | throwaway894345 wrote:
             | I don't know. A lot of the criticism of Go is just wild
             | over-exaggeration. Is "tech-splaining" just setting the
             | record straight? For example, the guy upthread is
             | insinuating that Go is roughly on-par with C++ as they both
             | have nonzero amounts of undefined behavior. Having used
             | both languages extensively, I can say for certain that you
             | will run into a lot more UB in C++ than in Go, and it will
             | be a lot harder to debug when it happens. Similarly, the
             | guy downthread who is making a mountain out of Go's
             | nullable pointers (and all of the other people who complain
             | about Go's type system)--yeah, they are strictly worse than
             | Rust's enums, but the overwhelming majority of all software
             | ever written has been built with languages that have
             | nullable pointers and a good chunk of that is with
             | languages that have _fully dynamic type systems_ (not only
             | is the type system going to allow your null pointer access,
             | but it will allow all manner of type errors!). Is it
             | "smugness" to put criticism into perspective?
        
               | lamontcg wrote:
               | If you look in the past history of language features that
               | are being added or might be added (e.g. generics) you can
               | find a whole lot of past explanation of why the language
               | doesn't need those features in the name of simplicity and
               | why the reader just doesn't understand the brilliance of
               | Rob Pike.
               | 
               | Reminds me a lot of the Mac forums where every poor
               | feature in an Apple product is explained with a "let me
               | help you understand why you're thinking about it wrong"
               | kind of answer -- up until Apple fixes the bug /
               | implements the feature to the applause of the same people
               | who were talking down why it would ever need to get
               | fixed.
               | 
               | And just the tone of the article here turns me off in the
               | way it begins with a bunch of quotes and philosophy. I
               | actually agree entirely with "Don't communicate by
               | sharing memory. Share memory by communicating" but I
               | figured that out myself in probably 2003 writing crappy
               | perl scripts that utilized parallelism but I wanted to
               | aggressively avoid all the concurrency pitfalls. Actor
               | models and Erlang later made completely intuitive sense
               | to me. The principle is entirely correct, but its fucking
               | weird that a programming language needs to have a list of
               | "proverbs".
        
               | throwaway894345 wrote:
               | > If you look in the past history of language features
               | that are being added or might be added (e.g. generics)
               | you can find a whole lot of past explanation of why the
               | language doesn't need those features in the name of
               | simplicity and why the reader just doesn't understand the
               | brilliance of Rob Pike.
               | 
               | Isn't this just nutpicking[0]? You have that with every
               | language. I can criticize C++ on proggit right now and 3
               | or 4 people will respond "C++ has _every_ language
               | feature, just use the ones you want /like/etc and ignore
               | the others, your problems are invalid!". Similarly, on an
               | OCaml forum I can find a dozen people who tell me if Jane
               | Street hasn't run into my problem before and solved it,
               | then it's not a real problem and I'm dumb for trying to
               | do it. I can post here or on r/rust (or proggit) and
               | people will tell me that Rust is strictly faster/easier
               | to develop with than any GC language because in the worst
               | case you can always throw `Rc<T>` on everything. I can
               | post on r/python about how hard it is to optimize Python
               | and people will tell me I'm dumb and I can just use
               | multiprocessing, rewrite the slow bits in C/Cython/etc,
               | use numpy/pandas/etc. I can merely _register for an
               | account_ on a Java forum and be berated for my low
               | intelligence. :)
               | 
               | > The principle is entirely correct, but its fucking
               | weird that a programming language needs to have a list of
               | "proverbs".
               | 
               | I don't feel as strongly as you do, but I agree that
               | proverbs and analogies are pretty low quality and invite
               | more confusion than they address. Probably only a rung
               | above analogies and a rung below quotations.
               | 
               | [0]: https://rationalwiki.org/wiki/Nutpicking
        
               | lamontcg wrote:
               | > Isn't this just nutpicking[0]?
               | 
               | From that article, I'm specifically addressing "Does this
               | movement _promote_ crazies?"
               | 
               | I'd argue the sloganeering attracts them like moths to a
               | flame.
        
               | throwaway894345 wrote:
               | I don't know what is meant by "sloganeering" but if
               | writing a full, nuanced article (e.g., TFA) fits the bill
               | then I'm not sure I agree with your conclusion...
               | 
               | In other words, I agree that "sloganeering" attracts the
               | nuts, but I'm not sure I agree that TFA is sloganeering.
        
       | draw_down wrote:
       | I wish some of the chapter-and-verse about "obviousness" that
       | gets constantly recited with regard to the language was also
       | applied to the tooling.
        
       | yoursunny wrote:
       | If I have a uint64 counter incremented by C code and read by Go
       | code, is it safe to do so without using any locks and atomic?
        
       ___________________________________________________________________
       (page generated 2021-07-12 23:00 UTC)