Hacker News new | past | comments | ask | show | jobs | submit login

Go doesn't protect you against race conditions, it merely offers some concurrency tools. There is nothing to declare ownership of objects in memory. So the compiler doesn't (can't) complain if you share memory and access it simultaneously. At best, there are runtime checks. Rust does offer compiler protection against that.

Edit: "merely", relatively to Rust :) on an absolute scale, still way better than C for concurrency.




As a total beginner (learning programming by myself since 2 or 3 years), i am always asking myself, how often "little" things like race conditions break something in production. Sure thing, some applications need to be safe-super-safe. But is it worth to switch over from go to rust as a beginner, since go is the unsafer language? I know, that there is no ultimate language. But i always asked myself i am missing a point, since i never really had problems like that occur. I also wrote something little in clojure (wanted to write something in lisp, and clojure presented itself somehow modern). But many people told me about how bad clojure is, because it is not type-safe. So i switched to go. And i have to say, it is really nice, to be forced to use the right types as input. But will i have the same experience when i switch over to rust and think to myself: "Whoa yes, i never though about that aspect, but it really helps me as a beginner who tends to write slobby programms!"?


> how often "little" things like race conditions break something in production

Often. Way too often. And it always break at 3 in the morning when you are hiking somewhere where there is no internet connection.

Besides, race conditions tend to break on the worst possible ways. It either silently corrupts data, or makes your system stop working but stay active, so the OS does not know it must launch a new instance.


Think about effort in vs results gained. Example:

You can drive nails with a knife by holding the nail and hitting it with the side of the knife. It takes work to do right with you occasionally cutting yourself. Some people will even loose a hand.

You can use a hammer. You will get the nails in smoother with less rework required. You occasionally hurt yourself but not as bad as a knife. You can also smash the nerves to point you don't feel anything.

You can use a nailgun with safety switch. It's ultra-fast and can't hurt you if you're aiming where the nails go. It cost a bit more. It can hurt you if you turn the safety off or aim it at yourself. Simpler than mental effort into using knives or hammers safety. Less tiring, too. Some people even think they're fun.

That's C, C++, and languages that are memory-safe respectively. Rust's advantage of no GC is like a nail-gun that costs same as a hammer. It has the benefits but not the primary disadvantage. If you're a craftsman, you're time and energy are valuable. Use the best tools you can for whatever work you're doing. That is, what gets it done quickly, done right, and easy to fix later w/ smaller problems.


>As a total beginner (learning programming by myself since 2 or 3 years), i am always asking myself, how often "little" things like race conditions break something in production

Rookie mistake (and a shoot-yourself-in-the-foot-at-2-am) mistake coming up:

package main

import ( "fmt" "sync" )

func main() { var wg sync.WaitGroup

for i := 0; i < 10; i++ { wg.Add(1)

		go func() {
			fmt.Printf("i= %d\n", i)
			wg.Done()
		}()
	}
	wg.Wait()
}

https://play.golang.org/p/XDzFq_XK_1

And what about this code:

package main

import "fmt"

func main() {

var intArray []int for i := 0; i < 1000; i++ {

		intArray = append(intArray, i)
		fmt.Printf("i= %d\n", i)
	}

	fmt.Println(intArray)
}

https://play.golang.org/p/UuI4uESZ_f

If you don't care if intArray is in the proper order, you might do something like this:

package main

import ( "fmt" "sync" )

func main() { var wg sync.WaitGroup var intArray []int

for i := 0; i < 1000; i++ { wg.Add(1)

		go func(i int) {
			intArray = append(intArray, i)

			fmt.Printf("i= %d\n", i)
			wg.Done()
		}(i)
	}
	wg.Wait()
	fmt.Println(intArray)
}

What's wrong?

On my multi-core computer (though not on playground), len(intArray) could be as low as 500!

Why?

Because a = append isn't atomic.

Go, which is so pedantic about "stupid" mistakes (including something, changing your mind, and not "unincluding" it), didn't catch this. Not an error and no warning.

But the worst part is that when you loop until 10, it works. You can unit test it, integrate test it, and have it break at any point in time.


You can also check out some CMU public calendars and read their lecture notes about safe standards in programming or get introduced to contracts just so you get better at eyeballing your own libraries to look for obvious errors https://www.cs.cmu.edu/~rjsimmon/15122-s16/schedule.html

They also have distributed programming course lecture notes that are open to the public with golang specific info on how to deal with race conditions.

You'll have the same experience in Rust if you research why they've done certain things, but can quickly get lost on the mailing list unless you've read books like EoPL http://www.eopl3.com/


> i am always asking myself, how often "little" things like race conditions break something in production

It can basically corrupt the whole program.

I bet half go apps out there have data race. People who boast about Go's simplicity can't even see the elephant in the room. A simplistic type system doesn't fix unsafe concurrency. You'd think that safe concurrency would be an important design goal for a highly concurrent language, well apparently it isn't.


Speaking as an experienced programmer: all the time, in subtle ways, sometimes that you don't notice for a long time. Sometime benign, sometimes your data has been being corrupted for months before you notice.

My standard advice on concurrency is, quite simply, don't. It always looks way simpler than it actually is. If you're doing something that _requires_ it, get all the help you can. Type-level enforcement sounds _excellent_ to me.


Yep. I deal with a platform that has some sort of race and accesses a destroyed object. It's incredibly tricky to find it, and customers only hit it at the worst time, with certain traffic loads. It's a subtle bug that's lasted years and is in a codepath used millions of times a day.


If you want to avoid data corruption in concurrent software, there's always Erlang as an option.


I haven't checked Erlang too well, but is it so that the concurrency in Erlang software is basically actors only? Of course in this space you can also use Akka and even Rust and C++ have actor libraries available.

But there are definitely use cases where you don't want to use actors, where you might want to compose several futures together without the overhead of actor mailboxes.


> it so that the concurrency in Erlang software is basically actors only?

Erlang's concurrency is "don't communicate by sharing, share by communicating" enforced at the language level: an Erlang system is composed of processes which each have their own heap (and stack) and an incoming "mailbox", an Erlang process can only interact with the world by calling BIFs (built-in "native" functions, Erlang syscalls if you will) or sending messages to other processes, and messages can only contain immutable data structures (mutation happens only at the process level).

Of course one of the sources for this design is that Erlang comes from a world where 1 = 0 (if you don't have redundancy you don't have a system) thus two processes may live on different nodes (erlang VMs) on different physical machines and shouldn't behave any differently than if they were on the same node.


So basically it's like Scala/Akka, except in Erlang you can send functions which is a nice feature. One thing that has kept me from using it in production is its dynamic typing. Once you've been spoiled with a good type system, it's quite hard to go back to dynamic typing.


Akka is modeled on Erlang but the JVM has very different characteristics. The Erlang VM is optimized for immutable languages and message passing. GC is very different (almost a non-issue) because of the process model.

And pattern matching in Erlang is a joy every developer should experience. I've not been impressed with my limited exposure to Scala, which also bears the burden of trying to be a kitchen sink language.


In fairness, Go has an extremely good race detector (run/test with -race to enabled it), and makes no guarantees about memory or correctness if you write code with races in it


Last time I checked, the race detector could only find a certain percentage of data races – those easier to detect .

The crux with such systems is you tend to rely on them...


It becomes a problem when you're building applications with complex interactions, and is even worse with large codebases that are tough to debug and reason about.


Data races definitely happen in the real world and they are awful to track down, because they only happen n% of the time. All of my least favorite bug-fix experiences have been data races.


I don't know why you are downvoted since your question is legit.

I'll answer given my own experience :

At my former company, we had a websocket-based service that allowed symetric communication between the clients. We had around 10% connection failures, and we thought it was causes by websocket well-known incompatibilities with some network stack, and we had a fallback to ajax polling.

Several month later, someone touched this part of the code and found a data race. After we fixed it, the failure rate dropped to 5%.

Is 5% of failure big ? It depends on your business. In our case it wasn't too bad because we had a backup plan. But if every customer have a 5% chance of application breakage each time he connects, you may have trouble building a big user-base.


These are some of the bugs I hate the worst. When you have a plausible explanation about why it's slow, or fails sometimes, and it isn't under your control (or is way out of your purview), it's far to easy to stop lookingand not find your own bugs that exacerbate the problem. Because if you don't have a bug, and it is all that external problem, you just wasted all that time looking. Finding that bug later is especially painful, as you realize a little more time initially may have saved so many problems later. :/


I've had to deal with several race conditions. They're easy to miss and difficult to debug once you have a system that starts scaling up to millions of transactions per day.




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: