
Mistakes C/C++ Devs make writing Go - beliu
https://about.sourcegraph.com/go/gophercon-2018-5-mistakes-c-c-devs-make-writing-go
======
ktpsns
Here's a newbie's bit off topic question: As a C++ dev who wants to write
"slightly less low-level code", should I go for RUST or GO? My intuition says
Rust is more like a safer version of C++ while Go is more half way to
Python/Julia. Do you agree with this sloppy assessment?

(Please, don't start a religious war on programming languages. We are all
grown ups)

~~~
paulsutter
Yes, you’re right. The Go team was originally targeting use in C++-like
applications, but was surprised to see Go catching on more among Python
programmers than C++ programmers.

I would also suggest trying Python itself (why not go all the way?).
Personally I write quick hacks in Python and real code in C++, and find Go to
be an interesting middle ground. Python will do more to stretch your idea of
programming. Maybe someday Go will get numpy and tensorflow.

~~~
pietroglyph
I'm not sure if this is what you meant, but there are actually supported
Tensorflow bindings for Go. They're quite usable (at least for me when I used
them for inference), relatively idiomatic, and well documented. See
[https://godoc.org/github.com/tensorflow/tensorflow/tensorflo...](https://godoc.org/github.com/tensorflow/tensorflow/tensorflow/go)

~~~
weberc2
Also I’m not sure exactly what Numpy is but Go probably has an analog as it
already has a suite of scientific computing libraries (check out gonum as a
starting point).

------
shoo
re: "# channels < # goroutines"

    
    
      func doSomethingTwice() error {
          // Issue occurs below
          errc := make(chan error)
      
          go func() {
              defer fmt.Println("done wth a")
              errc <- doSomething("a")
          }()
          go func() {
              defer fmt.Println("done with b")
              errc <- doSomething("b")
          }()
          err := <-errc
          return err
      }
    

> When one routine writes to the channel, the program exits and the other
> goroutine is lost, building up memory use as a results

I'm not very familiar with go, but my guess would be that since the two
goroutines send to an unbuffered channel that is read at most once, the
"slower" of the two goroutines will sit there blocking attempting to send to a
channel that will never be read. So this would "leak" a goroutine that would
consume resources while the process was still running, but it doesn't have
anything to do with the program exiting.

[https://golang.org/ref/spec#Channel_types](https://golang.org/ref/spec#Channel_types)

[https://golang.org/ref/spec#Send_statements](https://golang.org/ref/spec#Send_statements)

> How do we fix this? We simply increase the number of channels to 2

The fix is okay but the language is a bit hazy, there's still only one
channel, but now it's a buffered channel with capacity to hold up to 2
messages, so the two goroutines don't need to block waiting for a receiver to
be ready to synchronously receive the message they are sending.

~~~
geocar
> The fix is okay but the language is a bit hazy, there's still only one
> channel, but now it's a buffered channel with capacity to hold up to 2
> messages, so the two goroutines don't need to block waiting for a receiver
> to be ready to synchronously receive the message they are sending.

I was very disappointed with this explanation as well. The documentation is
less than direct on this point, and suggests goroutines "execute
independently", so my only conclusion is that the author doesn't understand
channels very well, and was perhaps led to not understand them well.

    
    
        01  func doSomethingTwice() error {
        02      // Issue occurs below
        03      errc := make(chan error)
        04
        05      go func() {
        06          defer fmt.Println("done wth a")
        07          errc <- doSomething("a")
        08      }()
        09      go func() {
        10          defer fmt.Println("done with b")
        11          errc <- doSomething("b")
        12      }()
        13      err := <-errc
        14      return err
        15  }
    
    

If the programmer understands the control flow is something like: 03, 06, 07,
10, 13, 14 then they're not going to be confused, and they're certainly not
going to "fix it" by increasing the _buffer_ , they'll fix it by reading from
the channel twice, and guarding against this by closing the channel. The real
question is how to explain to the programmer what the semantics of channels
is:

A goroutine blocks on read or write of an unbuffered channel (what we're
seeing here).

The garbage collector is for simulating an infinite memory machine only. It is
not for "cleaning up" after you.

The issue comes up when go programmers learn about runtime.GOMAXPROCS much too
early, so they _learn_ goroutines as threads, and they _guess_ that the thread
will be cleaned up when "garbage collected" because that's how Python works
(or some other "garbage collected" language they might be familiar with).
They're further confused because golang values have a "finalizer" so they
might just _assume_ that the thread finalizer (somehow) kills the thread, or
the channel finalizer (somehow) closes the channel.

Perhaps if they noticed that writing to a closed channel causes a run-time
panic, they might think these semantics are unlikely.

The point about buffering is to be able to write twice without blocking. Yes,
you get 03, 06, 07, 10, 11, 13, 14 but how important was that really? If you
did it just because you don't want to "leak memory", then you still don't know
what's going on.

~~~
cdoxsey
Increasing the channel size to 2 is the correct fix.

You don't want to read twice because then you would block waiting for both
messages.

When the channel size is two the two inner writing goroutines can succeed on
their write to the channel even if nothing reads from it.

The channel has three references, the outer receiver, and the two inner
senders. The receiver finishes as soon as its able to receive the first
message.

So what happens is one of the messages is sent and received, the other is sent
and just sits in the channel. Then the channel has no more references and is
garbage collected, so there's no memory leak.

Also goroutines are scheduled onto threads. So these orphaned goroutines that
can't complete eat up memory (their call stack + the channel which can't be
completed) as well as put pressure on the scheduler. (though the scheduler is
efficient and can handle millions of goroutines) That's why it makes sense to
refer to this as a memory leak.

~~~
geocar
> Increasing the channel size to 2 is the correct fix.

Sometimes. Sometimes it is not. For a trivial example, a unix-style wait would
be done by reading twice. A job queue would be another.

It is not, for the reasons specified in the article, which falsely states that
the buffer length and the number of goroutines has some relationship (#
channels < # goroutines).

~~~
cdoxsey
The channel size needs to match or exceed the number of producing goroutines.
Anything less and the some of the goroutines will block attempting to send to
a channel with no receiver.

The example in the article is a common issue that comes up in go programs, and
the suggested fix is what is usually recommended.

The other way to solve it would be to read the first error and then up another
goroutine to read the remaining error, but that seems more complicated to me.

~~~
geocar
> The channel [buffer] size needs to match or exceed the number of producing
> goroutines

This is not true, for the reasons I outlined.

> The example in the article is a common issue that comes up in go programs,
> and the suggested fix is what is usually recommended.

That may be, but it doesn't change the fact that go programmers that take this
advice are creating a kind of cargo-cult around channels and goroutines that
will hold them back.

> The other way to solve it would be to read the first error and then up
> another goroutine to read the remaining error, but that seems more
> complicated to me.

If all you ever do with channels and goroutines is simulate threads, you're
missing out. Erlang programmers might more-easily appreciate this given that
they have to go through an incredible contortion to get this feature that go
provides for free.

------
agallego
This has no c++ content. only C. the mistakes highlighted are just not
representative of a team of C devs. the resource leak is specially a bad
example because there better examples where go safety is a real benefit.

~~~
pjmlp
A reflection of lots of "C++" code I see in the enterprise, what I call "C
compiled with C++ compiler", because usually it doesn't even make use of C
with Classes kind of features.

Also why the C++ community has started to focus on teaching C++ the right way.

------
alexott
Can’t leave comment in blog, but the first two listings are broken - it looks
like there is no empty line after ``` in listing line, and before the same in
listing two. So enumeration isn’t rendered correctly

------
raverbashing
Good tips, unfortunately, the explanations are not very clear and it's written
in poor English, this gets very distracting.

~~~
SheinhardtWigCo
Which part are you looking at? It all reads quite clearly to this native
English speaker.

------
tomohawk
Regarding the defer statements in loops. It is better to just not ever do
that.

Instead, move the defer outside of any loops, and have the defer check the
state of the variable to decide action. For example, in the case of an open
file, the loop should close the file and nil out the reference. In the defer,
if the reference is not nil, it closes the file.

~~~
masklinn
The issue outlined in the article is not defer in a loop, it's defer in an
_infinite_ loop.

------
dis-sys
for goroutine leaks, I have been using the leaktest package made by fortytw2
[1] in all my tests, pretty useful for my day-to-day dev work.

the article is pretty cool, especially when the author actually has a family
name Check. :)

[1]
[https://github.com/fortytw2/leaktest](https://github.com/fortytw2/leaktest)

------
axilmar
I wouldn't want to use a language that I cannot predict how memory allocation
would work. I'd like to have every inch of performance in my disposal for the
tasks I am interested in (video games, simulations, data mining).

------
nialv7
Sounds more like design mistakes of Go.

------
ncmncm
1.There is no such language as C/C++.

2\. There are no developers of C/C++.

3\. C and C++ are distinct languages.

4\. Anybody advertising for a C/C++ developer is insufficiently aware of
software requirements to provide meaningful employment.

5\. C++ programmers and C programmers have different responses to unfamiliar
languages.

6\. Some C programmers would like to be thought of as skilled "C/C++"
programmers. There are none. Thus, they are not.

7\. Many C++ programmers are capable of altering C code. They, also, are not
"C/C++" programmers. When altering C code, they are programming in C, not C++.

8\. C++ programmers and C programmers make different kinds of mistakes.

9\. Anyone titling an article about "C/C++ programmers" make is making a
fundamental category error, and has self-identified as lacking insight.

10\. Anyone titling an article about mistakes "C/C++ programmers make" refers
to an empty set of programmers.

11\. People new to Go make mistakes. (One such mistake might be using Go at
all; that is not decided.)

12\. There will be some intersection between mistakes made by any two groups
of programmers.

13\. The intersection between mistakes made by Java programmers and Javascript
programmers does not define a "Java/Javascript" language, nor a set of
"Java/Javascript programmers", despite any similarity of names or surface
syntax between the two.

~~~
cmrdporcupine
I guess you're being downvoted for tone, but I agree with you.

I program professionally in C++. Waltzing into a non-C++ C code base requires
a strong mental shift. I work with embedded programmers new to our team who
have a strong history of C programming, and for them, C++ is a very steep hill
to climb to become proficient.

They are two languages with interoperability and semantic similarities that
share a compiler infrastructure and a runtime and memory model.

But modern C++ programming differs in drastic ways from C such that I don't
think you can call them a unified language and brush over it with 'C/C++
programmers'

~~~
pjmlp
C/C++ Users Journal

[https://en.wikipedia.org/wiki/C/C%2B%2B_Users_Journal](https://en.wikipedia.org/wiki/C/C%2B%2B_Users_Journal)

Yes the languages are different, I would like to have C nuked instead of
having to use it from time to time, but I agree with using C/C++ instead of
writing _C and C++_ all the time.

It is a matter of English style only.

[https://www.grammarly.com/blog/slash/](https://www.grammarly.com/blog/slash/)

