In practice, there is very little difference: both represent separate units of execution whose primary interface with the outside world is via messages.
The differences are in the implementation details of the languages. Here are a few such details:
- Channels in Go are typed; if you want to send messages with different data, you need separate channels. With Erlang, one
receive gets everything sent to the process and must pattern-match (in Go, you would use a
select with multiple cases, so the code would look very similar, just with different channels).
- Anyone can read or write a Go channel. In Erlang, anyone can send to a process, but only that process will receive. This becomes important if you want to divide a task between multiple workers: in Erlang you need to create a distribution process, while Go can simply share a channel.
- Erlang provides a (mostly) transparent path to distributing processes on multiple hosts/VMs. Goroutines are confined to a single process (although there are libraries for distribution).
- Error handling is very different. Erlang treats each process as independent: an error in one process (say divide-by-0) will not affect any other processes unless you explicitly link them (although something waiting for a message from the dead process will hang). Goroutines all run in the same process space; a divide-by-0 will take down the entire program.
- In Erlang, data is immutable. This means that all communication between a process and the outside world takes place through messages. Go allows you to share state between goroutines (although you shouldn't).
This last point is, I think, the most important. While both use messages as the primary means of communication, Erlang makes much stronger guarantees about how and when state can change.