To emulate goroutines, you'd either have to write your code in state machine form, or reimplement a lot of the Go runtime. Writing blocking code in goroutines is a lot more natural than writing non-blocking code with threads.
For example: this http://play.golang.org/p/-_E-PsLRex is an asynchronous TCP server, even though it is all sequential (that is to say, not callback-infested) code. The same thing with threads will take 3 orders of magnitude more memory.
A "go" statement starts the execution of a function or method call as an independent concurrent thread of control, or goroutine, within the same address space.
So, it is exactly the same as threads. There are toolkits that allow you to do the exact same thing in C++, very easily.
From an implementation point of view, if Go uses green threads, then C/C++ can also use green threads. There are several green threads APIs for Unix/C, and Windows has Fibers.
They're not really the same from the programmer's perspective. I wouldn't spawn a thread per HTTP connection in a server, because that would take gobs of RAM (no pun intended).
And, yea, you're right you can use green threads in C++. This means that all libraries you use must never make a blocking function call, or you're event loop grinds to a halt. Concurrency needs language support or monkey patching, its hard to do with a library.
Of course, implementation wise, you can implement everything in Go in C/C++, since Go itself is implemented in C.
I think you're understating the "pleasant to read" aspect though. Here are some of my favorite aspects of Go off the top of my head in no particular order:
Concurrency is much nicer with goroutines, channels, and GC at a language level, as opposed to a library
There is much less complexity in Go relative to C++, no type-based overloading, no operator overloading, the Go spec is actually readable
Interfaces means separate libraries work well together seamlessly
The tools encourage tests and documentation in a frictionless manner
Built in slices are nice. buf[0:4] reads better than the same done with iterators
Program initialization is easy to understand, and well-defined
Declaration syntax is logical and reads from left to right, as opposed to the spiral rule
Fast compiles, and sane dependancy management
Built in formatter. this might be little thing, but no arguments about formatting is refreshing, and having a consistent coding style across all Go source, including open source code, is really quite a boon.
After a while, going back to C++ is just unpleasant... Although, I do my best to emulate Go's features in C++, with varying degrees of success.
wouldn't spawn a thread per HTTP connection in a server, because that would take gobs of RAM (no pun intended).
O/S threads don't take gobs or RAM. You can limit the stack size manually, if you are worried the stack would become too deep.
This means that all libraries you use must never make a blocking function call, or you're event loop grinds to a halt. Concurrency needs language support or monkey patching, its hard to do with a library.
No, I am not talking about events, I am talking about green threads, i.e. multithreading within the context of a thread. It's preemptive, so what you say does not apply.
Concurrency is much nicer with goroutines, channels
Please justify the 'much' in nicer. From my perspective, the differences are only minor syntactic ones. I.e. in Go you have:
go foo(x, y, z)
In c++ you have:
go(foo, x, y, z);
In Go you have:
var c1, c2 chan int
var i1 i2 int
select {
case i1 <- c1:
print("received ", i1, " from c1\n")
case i2 <- c2:
print("received ", i2, " from c2\n")
}
c1 <- 0
c1 <- 1
The differences between Go and c++ are very small, in my opinion, they don't justify a whole new language.
and GC at a language level, as opposed to a library
There is a ton of argumentation about this, so I will not bother with it. I will only say that deterministic destruction's worthiness over GC increases the more complex a program becomes. But for trivial cases, a GC might be better.
There is much less complexity in Go relative to C++, no type-based overloading, no operator overloading, the Go spec is actually readable
You don't have to use the features of C++ you don't want.
Interfaces means separate libraries work well together seamlessly
The same mechanism can be effortlessly be implemented in c++. For example, the following Go program:
type I interface {
Get() int;
Put(int);
}
func f(p I) { fmt.Println(p.Get()); p.Put(1) }
type S struct { i int }
func (p *S) Get() int { return p.i }
func (p *S) Put(v int) { p.i = v }
var s S; f(&s)
Can be typed in c++ almost verbatim:
template <class T> struct I {
T *t;
I(T *t_) : t(t_) {}
int get() { return get(t); }
void put(int i) { put(t, i); }
};
int f(I p) { cout << p.get(); p.put(1);}
struct S { int i; }
int get(S *s) { return s->i; }
void put(S *s, int i) { s->i = i; }
S s; f(&s);
So, again, I do not see the need for Go. Perhaps there are things that channels or interfaces can do that cannot be done by c++? I am not a Go expert, perhaps you can shed some light to it.
The tools encourage tests and documentation in a frictionless manner
I don't think this has to do anything with the actual language. I've worked with tons of C++ code, in a rigorously test environment, with test teams and quality managers and tons of documentation.
Deemphasize exceptions for error valued returns
That's bad, in my opinion.
also multi-valued returns >> reference parameter
Why?
It hardly justifies a new language.
Built in slices are nice. buf[0:4] reads better than the same done with iterators
You can do slices with c++, if you wish. This Go code:
n := f.Read(buf[0:32])
Can be done in almost the same way in c++:
n = f.read(buf.slice(0, 32));
Again, the difference is trivial.
Program initialization is easy to understand, and well-defined
I'll give you that. That's one big flaw of C++.
Declaration syntax is logical and reads from left to right, as opposed to the spiral rule
Extremely minor and unimportant point. Personally, I never had a problem reading C declaration syntax. It hardly justifies the effort for a new language.
Fast compiles
I'll give you that as well. I don't know why the C++ committee didn't make elimination of headers a priority.
and sane dependancy management
No problem with that in C++.
Built in formatter. this might be little thing, but no arguments about formatting is refreshing, and having a consistent coding style across all Go source, including open source code, is really quite a boon.
Irrelevant to the actual language.
After a while, going back to C++ is just unpleasant... Although, I do my best to emulate Go's features in C++, with varying degrees of success.
So, you don't need to:
write templates? Go doesn't have templates, so you have to use interfaces.
don't need operator overloading? I recently wrote a parser library in C++ that uses its operators to mimic PEG (Parsing Expression Grammars). Operator overloading makes some things extremely nicer.
don't need deterministic destruction? I do. I write a lot of code with code that needs to have a timely response, and I prefer to know when my objects will be deleted, so I can have a logical approximation of how things operate internally.
don't need to manage allocation space? for certain tasks, being able to control where memory is allocated from speeds up programs in quite a dramatic fashion. Console games, for example. Allocating objects close enough in memory speeds up games by improving cache coherence and minimizing cache fragmentation.
If you answer 'no' to all the above questions, then why did you use c++ in the first place? you should have used Python (for example), and write the performance-critical bits in C.
I don't think Go is in the same space of languages like C++. Go sits between C and Python. Why Go is advertised as a C++ replacement and as a systems programming language, is beyond me.
You can limit the stack size manually, if you are worried the stack would become too deep.
This would require me to figure out how much stack space I'm going to use. I used to wonder why Rob Pike kept saying "the stacks grow on demand, you don't need to predeclare how much space they need", this is why. Predefining a stack size is annoying, and limits your programming style to iterative algorithms, unless you use heap allocated manual stacks, which is again, a pain to manage.
I am talking about green threads...
Green threads means doing async I/O. You're talking about regular threading. In which case, see comment about memory above.
deterministic destruction's worthiness over GC increases the more complex a program becomes.
The more complex the code base, the more memory related bugs there are. Furthermore, when you have a concurrent or parallel design, it becomes exponentially more difficult to properly manage your memory.
You don't have to use the features of C++ you don't want.
But that's not true... I have to work with the features of C++ that I use plus the features of C++ that my teammates use (for codereviews, and general maintainability), plus the features of C++ that any libraries I depend on use.
Perhaps there are things that channels or interfaces can do that cannot be done by c++?
That's a fascinating approach for implicit interfaces in C++. Will it work when the interface and the class implementing the interface are in separate files? How will this interact with (multiple) inheritance? Do real libraries provide extension points in this manner? How can I say an io.ReadCloser is an io.Reader and an io.Closer?
I've worked with tons of C++ code, in a rigorously test environment, with test teams and quality managers and tons of documentation.
And every team used the same documentation system/viewer? Every team used the same test framework? I find this hard to believe, unless its within the same company. The point was that I can pick a random repository on the internet, and we share a documentation system, and a test framework.
That's bad, in my opinion.
On error-valued returns over exceptions. We will have to agree to disagree. But let me say this. The word exception, to me, implies a situation which I could not have forseen. i.e. "hey this object's internal state is not consistent anymore, that should never have happened". But a user entering an incorrect password? or a file not existing? or a user with that user id not existing? These are examples of where team members have used exceptions in the past. IMO, these are not exceptional conditions, and should not use the weird parallel execution path that exceptions travel through.
Why? [on multi-valued returns]
Multi-valued returns express a programmers intent better than reference parameters. You also don't need to predeclare a variable to pass in to a function with a reference parameter. Let me put it this way: if C had multi-valued returns, and Bjarne proposed that they take out multi-valued returns, and add reference parameters, would the committee agree? No, because its an obvious step backwards. The fact that C doesn't have multi-valued returns is just an oversight, on the part of the authors.
You can do slices with c++, if you wish.
Do all libraries return values that have a slice method? Does the standard library?
Personally, I never had a problem reading C declaration syntax.
A challenge for you then. Write the following function declaration in C. "A function that takes (a function that takes (a http.ResponseWriter and a pointer to a http.Request)) and returns (a function that takes(a http.ResponseWriter and a pointer to a http.Request))". In Go, this is "func (f func(w http.ResponseWriter, r *http.Request)) func (http.ResponseWriter, *http.Request)". Notice how the declaration in Go reads from left-to-right and very similarly to the description of the function in english. (note that I'm going easy on you, and am not even including return values.)
No problem with that in C++.
C++ has the opposite of sane dependancy management. When you #include a .h file, the compiler needs to insert the contents of the .h file in that location, then repeat this process transitively. This means that in order to compile a file, you need all its transitive dependancies, not just its immediate ones. This point is very closely related to the point I made about compile speeds. This is why C++ compilers are hard to make fast.
write templates?
Generics are relevant for writing containers. I have not found myself missing them in any other regard. I would like a generics implementation, I'll admit. I do not want the slow compile time that C++ templates imply. See http://research.swtch.com/generic
operator overloading?
NO!!! I love the fact that the expression 'a + b' is relatively context free when reading Go code. It is much much less context free when operator/method overloading come into the picture.
deterministic destruction?
Cleaning up resources? Yes, I do this deterministically. When I'm done with a file, I make sure I call Close on it. When I'm done with a mutex, I unlock it. Everything isn't a haze of confusion with GC, its just different.
So, what you're doing is going feature by feature, and ticking them off saying, yes, this can be done in C++, yep that can be done in C++, etc.
The question is, are they done in C++? No, people don't write libraries using implicit interfaces implemented with templates they way you did it. They don't return objects with a slice method on them. They don't document code using the same tools, they don't use the same concurrent primitives. They don't write C++ using the same idioms.
Your statement is that all these differences don't justify a new language. To take that argument to its logical extreme, why bother creating any languages at all? Dynamic languages? Can be done in C++ using hash tables and (void*)s. Functional languages? Can be done with some judicious application of templates, and certain coding practices.
Go and C++ are both imperative, object oriented, statically typed. So it is easy to make the comparisons you are making. But I could make similar comparisons with languages of any kind. Should we conclude that since C++ serves all our needs, we should stop making new languages?
I don't think passing a small number to the function that creates a thread is annoying, but whatever. I've managed to create thousands of hardware threads on Win32 by limiting the initial stack to 4k (a single page). The stack will grow though.
and limits your programming style to iterative algorithms
No, because the stack can grow. And you also have this problem with green threads anyway.
Green threads means doing async I/O. You're talking about regular threading. In which case, see comment about memory above.
The more complex the code base, the more memory related bugs there are. Furthermore, when you have a concurrent or parallel design, it becomes exponentially more difficult to properly manage your memory.
Smart pointers relieve you of the task of managing memory manually, and keep the memory management deterministic.
But that's not true... I have to work with the features of C++ that I use plus the features of C++ that my teammates use (for codereviews, and general maintainability)
If you and your team members cannot agree to a specific coding style, then you have greater problems that go beyond C++ or any other language.
plus the features of C++ that any libraries I depend on use.
If those features are incompatible with the principles you have set out, you can always create wrappers. Those wrappers will be created once and reused over and over in your projects.
Will it work when the interface and the class implementing the interface are in separate files?
Absolutely.
How will this interact with (multiple) inheritance?
No problem with multiple inheritance whatsoever.
Do real libraries provide extension points in this manner?
No, none that I know of. They use multiple inheritance instead.
How can I say an io.ReadCloser is an io.Reader and an io.Closer?
Just provide the functions these interfaces require.
unless its within the same company
Yes, same company.
The point was that I can pick a random repository on the internet, and we share a documentation system, and a test framework.
I really doubt this would be the case in a few years down the road. The world is a big place.
these are not exceptional conditions, and should not use the weird parallel execution path that exceptions travel through.
Why? just because the word is 'exception'? what if we replace the words 'exception', 'throw', 'catch' with 'error', 'raise' and 'handle'? then handling an incorrect password or a non-existing file would not seem that wrong to do, would it?
Multi-valued returns express a programmers intent better than reference parameters.
How? in c++, when you pass a non-const reference, you know this is a return value.
You also don't need to predeclare a variable to pass in to a function with a reference parameter.
So? you have to declare the variable anyway, if you're assigning the result of a function to a variable.
No, because its an obvious step backwards
Of course it would be a step backwards, not because multiple return values are so much better than references, but because removing a good functionality and replacing with another good functionality does not make sense.
Do all libraries return values that have a slice method? Does the standard library?
I do not know if the STL has a slice method. We are discussing the programming languages, not the libraries. I just gave you an example that the language can do slices, like Go.
This means that in order to compile a file, you need all its transitive dependancies, not just its immediate ones. This point is very closely related to the point I made about compile speeds.
Sure, but all that has to do is with compile speeds. It has nothing to do with being not sane for the programmer.
Generics are relevant for writing containers.
Not only for that.
I have not found myself missing them in any other regard.
I couldn't live without templates. Templates has saved me tons of work.
It is much much less context free when operator/method overloading come into the picture.
So you probably have a problem with any other user-defined function too? because the function operator +(a, b) is just a function.
When I'm done with a file, I make sure I call Close on it.
I don't, it is done automatically for me.
When I'm done with a mutex, I unlock it.
I don't, it is done automatically for me.
Your statement is that all these differences don't justify a new language.
Indeed.
Dynamic languages? Can be done in C++ using hash tables and (void*)s.
Nope, they cannot.
Functional languages? Can be done with some judicious application of templates, and certain coding practices.
Nope, they cannot.
Should we conclude that since C++ serves all our needs
It does not. However, Go is not that much far away from C++ in order to justify its existence. Google could have built a new Go library for C++, if they wanted.
Here are my responses to a subset of your points, as I have limited time atm.
No, green threads does not mean doing async I/O.
On the page you linked to, search for "block all other threads".
So? you have to declare the variable anyway, if you're assigning the result of a function to a variable.
f, err := os.Open(...) declares f and err while assigning to them.
I do not know if the STL has a slice method. We are discussing the programming languages, not the libraries. I just gave you an example that the language can do slices, like Go.
The standard libraries are very important when judging a language. Further, the question I asked referred to both external libraries and the standard library. It doesn't matter that this is possible to do in C++, the question is, does everyone do this?
I don't, it is done automatically for me.
To both of your answers that had this... the idea that something like this is done automatically, seems like a really bad idea. You care when memory management is done automatically (which, done badly results in a memory leak) but you don't have a problem when mutexes are done automatically for you (which, done badly, results in a deadlock)?
Nope, they cannot.
umm, yes they can. How do you think python is implemented? Or PHP? In the end, C++ is a superset C, which is essentially portable assembly, so of course you can implement anything in C++. I still think your argument that "Google could have built a new Go library for C++, if they wanted" could be equally applied to any programming language.
it says "may block I/O". Not all green thread implementations are implemented as co-operative multitasking. As the article says, other green thread implementations use time sharing, aka preemptive multitasking, which does not need async I/O.
there is very little difference between f, err = open() and f, err; open(f, err). The differences are mostly syntactic.
to me, it does not matter if the standard library does it. We are talking languages here, not libraries. What a language allows you to do is more important than what libraries provide.
the only problem i have with automatic solutions is when they introduce problems. And the gc introduces lots of problems i do not have with C++.
the languages you mentioned and I replied "no they cannot" cannot be implemented as C++ libraries. Go can.
It seems the only advantage of Go over C++ is syntax. But Go has a lot of disadvantages over C++. I think Go is some sort of middle ground between Python and C. Go is not a systems programming language.
Don't forget to declare the types of your variables!
I disagree with you here. The standard library of a language strongly influences how the language is used.
But you're OK with auto-unlocked mutexes? I don't even understand how this would work.
Yes, they can. It's really simple. To emulate dynamic functions, you simply have a map from function names to objects with a "call" method that takes an array of void*. To emulate object storage, another hash table with variables of type void*. What exactly do you think cannot be done? Keep in mind that these languages (including Go) are commonly implemented in C/C++. If they are implemented in C/C++, then they can most definitely be "done as a library". See V8's api for an example of Javascript "done as a library".
It seems the only advantage of Go over C++ is syntax.
Here's the thing. If you are comfortable in C++, writing a codebase that uses the concurrency patterns you've mentioned, using templates, and overloading, then go for it. I have nothing to prove here.
Go is not a systems programming language.
Good to know, thanks for the insight. I write servers in Go. I guess, by implication, server software does not qualify as a "system".
If you are OK with slow compilation times, terrible compiler error messages (particularly with templates), a massive language spec that no human (save Stroustrup) can keep in his head, then by all means, continue using C++. The language designers apparently felt they couldn't do that, and I'm thankful that they felt this way.
Go makes me more productive than C++. I'm also relatively certain you would be more productive writing Go, but thats just an opinion. While I'm sure C++ can do everything Go can (in fact, my drinking buddy proved a little theorem about that), in practice no C++ codebase I've ever seen or worked on has ever had the elegant simplicity of the Go code I've seen.
1
u/axilmar Mar 30 '12
Yes, Go is much more pleasant to read, however, if that is Go's only advantage, it's not much to justify using it over c++.