Channels vs Actors

1,813 views
Skip to first unread message

Anto Aravinth

unread,
Mar 4, 2018, 12:00:54 PM3/4/18
to golang-nuts
Hello All, 

I'm new to golang and trying to understand the difference between channels and actors. From my knowledge:

1. Golang support channels, which is concurrently safe
2. Channels can be shared across goroutines that are running. (very much similar to messages)

Actors:
1. They are based on messages. 
2. Messages are immutable by nature. 
3. Actors work based on messages, concurrently. 

I find actors and channels much similar, may be I'm wrong here. Any help would be really good. 

Thanks, 
Anto;

Bakul Shah

unread,
Mar 4, 2018, 4:32:05 PM3/4/18
to Anto Aravinth, golang-nuts
Messages get sent to an actor's address and only that actor “reads”
from that mailbox. And there is no buffering. Channels don’t run any
logic (like an actor), they may be buffered and any goroutine with
access to a channel may read from it. All goroutines run in the same
address space. As actors don’t share memory, they can be distributed
or can even migrate from one node to another. Erlang is an actor
language. Go is not.
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jesper Louis Andersen

unread,
Mar 5, 2018, 4:53:32 AM3/5/18
to Bakul Shah, Anto Aravinth, golang-nuts
To add to Bakul's description:

A key difference in an actor-inspired model compared to a CSP-inspired one is that identity is subtly different. In Go, channels have identity and can be referenced. They can have multiple readers and writers. In Erlang, it is processes who has identity and you can send messages to processes. There can be multiple senders to the process, but only one process ever receives from the mailbox. This difference yields subtle differences in the way you often construct communication, but do note that the models are somewhat interchangable and what is written in one can be written in the other. Another subtlety is that multi-writer/single-reader can be targeted for optimization, and so on.

However, Erlang's message communication is not an implementation of the Actor model. According to Robert Virding, one of the Erlang authors, they didn't know about the actor model when they wrote the language, so if overlap is entirely by accident. Furthermore, Carl Hewitt who proposed the actor model has a paper in which he explains why Erlang doesn't implement it: in a pure actor system, you must be able to detect (groups of) processes who can make no progress ever and garbage collect them. Erlang doesn't do that.

Erlang's message communication is buffered. Locally, there is no bound on the message buffer and it is up to the programmer to write appropriate flow control. Across nodes, connections are by TCP and thus are limited by the usual TCP window which acts like a buffer: a sender who sends a message on a 0-window condition is blocked until the delivery can happen.

Crucially, Erlang relies on buffering for its implementation of a selective receive. You can set up a matching pattern and the mailbox will be searched, chronologically, until a message in the mailbox matches said pattern. If no message matches, the process is blocked until one does, or a timeout.

When a process has identity, you can monitor its lifetime. This is a central concept in Erlang systems: monitors (or links) are set up between processes and when they terminate, other processes will have a message delivered to their mailbox and can thus take action on the event. In Go, there is no way to find the identity of a goroutine, which yields a different programming model. Process trees are handled by the "context" package and lifetime is communicated through a specially constructed "Done" channel.

Haddock

unread,
Mar 8, 2018, 4:02:40 AM3/8/18
to golang-nuts
The main difference is that goroutines do blocking takes from channels whereas actors receive asynchronous events. This means that actors have to deal with all the situations in asynchronous programming that are inherently difficult to deal with (inherently means there is no general way to solve this and never will be).

The approach with blocking takes as in CSP and in Go runs into a problem when a thread blocked by an empty channel is lost for the application. Somehen then operationg system has no memory left to create new threads. This is being dealt with by having so called green threads (called goroutines in Go). Green threads have a reduced context and therefore consume less memory. On a machine with 4 GB RAM you can create about 2000 OS threads but about 80.000 goroutines (I once tried it out). So in Go you don't run into the problem that many empty channels with threads being blocked by them till they receive input can make the application go out of threads.

Actors have an advantage when used for distributed systems. If two actors on different boxes communicate with each other it is not a problem to make communication work without data loss through the principle of supervision. Supervision is the idea Joe Armstrong (the creator of Erlang) described in his PhD thesis. If two actors communicating with each other, realize something in the communication must have gone wrong (timeout waiting for ack, etc.), a third supervisory actor is informed which resets both actors who then restart from some safe point. There are hierarchies of supervisory actors to deal with the problem that also a superviory actor may hang or be subject to data loss.

Also, actors are served by normal threads (making use of thread pools to keep the number of memory intensive threads low). These normal threads do not have a reduced context, but a full one. This means that the information is present where some preemption took place where the thread needs to continue when resumed after suspension. Not so for green threads, because of their context being reduced, they are not pre-emptive. The developer in some situations needs to know that the current green thread/goroutine needs to yield for things to continue.

Channels in Go or CSP in general, on the contrary, rely on data exchange between channels to be free from any data loss. Otherwise the application may sit forever on some empty channel. This is no problem for channels inside the same instance of some program, but it is a problem in distributed systems that have to deal with communication through networks being unreliable.

In general with CSP-style concurrency it is much easier to get process synchronization right. If there is a problem, it is much easier to spot it and also to fix it. If have spent years of reproducing race conditions and deadlocks and fixing them with "traditional" programming languages without CSP. Saying that CSP makes concurrent programming much easier is something I can confirm from real-world experience. But they don't work well in distributed systems and when pre-emption is a necessity (shut down nuclear power plant when temperature threshold is exceeded here and now).

There is also CSP in the working for the JVM with Coroutines in Kotlin and project Loom for the JVM. The advantes of CSP for server-side programming and concurrency in general have become apparent also to other people ... ;-)

Anto Aravinth

unread,
Mar 8, 2018, 4:17:02 AM3/8/18
to golang-nuts
Thanks everyone and thanks Haddock for such a detailed response. Few questions:

On Thu, Mar 8, 2018 at 2:32 PM, Haddock <ffm...@web.de> wrote:
The main difference is that goroutines do blocking takes from channels whereas actors receive asynchronous events. This means that actors have to deal with all the situations in asynchronous programming that are inherently difficult to deal with (inherently means there is no general way to solve this and never will be).
Why we are calling out its difficult to handle asynchronous programming? I have done many node js project in real world, and for me if once you understand how asynchronous programming works, its simple mental model. I just feel interested, as you have said it would be difficult to handle them. 
 

The approach with blocking takes as in CSP and in Go runs into a problem when a thread blocked by an empty channel is lost for the application. Somehen then operationg system has no memory left to create new threads. This is being dealt with by having so called green threads (called goroutines in Go). Green threads have a reduced context and therefore consume less memory. On a machine with 4 GB RAM you can create about 2000 OS threads but about 80.000 goroutines (I once tried it out). So in Go you don't run into the problem that many empty channels with threads being blocked by them till they receive input can make the application go out of threads.

I guess, its 80,000. Should be a typo. Also all goroutines are called as green threads? I read somewhere they start with stack size of 4k, and expand when needed. Correct me if I'm wrong here.  

Actors have an advantage when used for distributed systems. If two actors on different boxes communicate with each other it is not a problem to make communication work without data loss through the principle of supervision. Supervision is the idea Joe Armstrong (the creator of Erlang) described in his PhD thesis. If two actors communicating with each other, realize something in the communication must have gone wrong (timeout waiting for ack, etc.), a third supervisory actor is informed which resets both actors who then restart from some safe point. There are hierarchies of supervisory actors to deal with the problem that also a superviory actor may hang or be subject to data loss.

Also, actors are served by normal threads (making use of thread pools to keep the number of memory intensive threads low). These normal threads do not have a reduced context, but a full one. This means that the information is present where some preemption took place where the thread needs to continue when resumed after suspension. Not so for green threads, because of their context being reduced, they are not pre-emptive. The developer in some situations needs to know that the current green thread/goroutine needs to yield for things to continue.

Channels in Go or CSP in general, on the contrary, rely on data exchange between channels to be free from any data loss. Otherwise the application may sit forever on some empty channel. This is no problem for channels inside the same instance of some program, but it is a problem in distributed systems that have to deal with communication through networks being unreliable.

In general with CSP-style concurrency it is much easier to get process synchronization right. If there is a problem, it is much easier to spot it and also to fix it. If have spent years of reproducing race conditions and deadlocks and fixing them with "traditional" programming languages without CSP. Saying that CSP makes concurrent programming much easier is something I can confirm from real-world experience. But they don't work well in distributed systems and when pre-emption is a necessity (shut down nuclear power plant when temperature threshold is exceeded here and now).

There is also CSP in the working for the JVM with Coroutines in Kotlin and project Loom for the JVM. The advantes of CSP for server-side programming and concurrency in general have become apparent also to other people ... ;-)

Nice, not sure if coroutines in kotlin are same as Goroutine -- mainly they also work as "green threads"?
 

Am Sonntag, 4. März 2018 18:00:54 UTC+1 schrieb Anto Aravinth:
Hello All, 

I'm new to golang and trying to understand the difference between channels and actors. From my knowledge:

1. Golang support channels, which is concurrently safe
2. Channels can be shared across goroutines that are running. (very much similar to messages)

Actors:
1. They are based on messages. 
2. Messages are immutable by nature. 
3. Actors work based on messages, concurrently. 

I find actors and channels much similar, may be I'm wrong here. Any help would be really good. 

Thanks, 
Anto;

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/uJxcfNsxh-0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts+unsubscribe@googlegroups.com.

Haddock

unread,
Mar 8, 2018, 4:37:20 AM3/8/18
to golang-nuts


Am Donnerstag, 8. März 2018 10:17:02 UTC+1 schrieb Anto Aravinth:
Thanks everyone and thanks Haddock for such a detailed response. Few questions:

On Thu, Mar 8, 2018 at 2:32 PM, Haddock <ffm...@web.de> wrote:
The main difference is that goroutines do blocking takes from channels whereas actors receive asynchronous events. This means that actors have to deal with all the situations in asynchronous programming that are inherently difficult to deal with (inherently means there is no general way to solve this and never will be).
Why we are calling out its difficult to handle asynchronous programming? I have done many node js project in real world, and for me if once you understand how asynchronous programming works, its simple mental model. I just feel interested, as you have said it would be difficult to handle them. 

The fundamental problem with asynchronous programming is that asynchronous messages that depend on each other from the application logic "may not meet" and miss each other. Let's say, several threads start to search through a pile of data starting from different offsets. Somehen one thread has found the needle, now the other threads do not need to continue searching any more. How to tell them to cancel their search? This is not easy and therefore is a good simple example to show problems in asynchronous programming, but only a simple one. There are hell of problem situations of similar nature with asynchronous programming ...

The problem with asynchronous programming is when doing concurrent things. When things run in parallel, there is no problem as there is no concurrency. Concurrency means that different threads need access to the data read and changed by other threads. A lot of business applications do things just in parallel. When someone changes his facebook page the change only concerns the account of that user, not of other users. Same for all other facebook users. So there is no concurrency and no difficulty in sychronizing shared state correctly between threads without race conditions, deadlocks, outlooks, live locks and all that.

Because many business applications are parallel and not concurrent, actors or just plain threads work well. This is then misused by the marketing guys by saying that actors make multi-threading easy. It is only true when things are in parallel and there is no or little concurrency. With heavy concurrency CSP is just great (albeit not pre-emptive and not when distributed).
 
 

The approach with blocking takes as in CSP and in Go runs into a problem when a thread blocked by an empty channel is lost for the application. Somehen then operationg system has no memory left to create new threads. This is being dealt with by having so called green threads (called goroutines in Go). Green threads have a reduced context and therefore consume less memory. On a machine with 4 GB RAM you can create about 2000 OS threads but about 80.000 goroutines (I once tried it out). So in Go you don't run into the problem that many empty channels with threads being blocked by them till they receive input can make the application go out of threads.

I guess, its 80,000. Should be a typo. Also all goroutines are called as green threads? I read somewhere they start with stack size of 4k, and expand when needed. Correct me if I'm wrong here.  

No typo, in my country the dot is the thousands separator ;-). But I will change to a comma when posting to an English speaking forum.

Nice, not sure if coroutines in kotlin are same as Goroutine -- mainly they also work as "green threads"?


You cannot have green threads on the JVM as the JVM does not have them. The workaround is to make use of fibers instead. See for example Quasar, which is already doing that. Project Loom inside Oracle is about making changes to the JVM to better support CSP-style additions. This video on youtube is a good presentation about fibers by the creator of Quasar and the owner of project Loom at Oracle.
 
 

Am Sonntag, 4. März 2018 18:00:54 UTC+1 schrieb Anto Aravinth:
Hello All, 

I'm new to golang and trying to understand the difference between channels and actors. From my knowledge:

1. Golang support channels, which is concurrently safe
2. Channels can be shared across goroutines that are running. (very much similar to messages)

Actors:
1. They are based on messages. 
2. Messages are immutable by nature. 
3. Actors work based on messages, concurrently. 

I find actors and channels much similar, may be I'm wrong here. Any help would be really good. 

Thanks, 
Anto;

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/uJxcfNsxh-0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.

Haddock

unread,
Mar 8, 2018, 4:46:55 AM3/8/18
to golang-nuts


Actors have an advantage when used for distributed systems. If two actors on different boxes communicate with each other it is not a problem to make communication work without data loss through the principle of supervision. Supervision is the idea Joe Armstrong (the creator of Erlang) described in his PhD thesis. If two actors communicating with each other, realize something in the communication must have gone wrong (timeout waiting for ack, etc.), a third supervisory actor is informed which resets both actors who then restart from some safe point. There are hierarchies of supervisory actors to deal with the problem that also a superviory actor may hang or be subject to data loss.

It would be a nice project to develop distributed channels in Go where dataloss through the wire cannot happen, because the implementation of those distributed channels relies on supervision. Maybe in Go 1.11 ? :-)

Jesper Louis Andersen

unread,
Mar 8, 2018, 7:21:55 AM3/8/18
to Haddock, golang-nuts
On Thu, Mar 8, 2018 at 10:37 AM Haddock <ffm...@web.de> wrote:
The fundamental problem with asynchronous programming is that asynchronous messages that depend on each other from the application logic "may not meet" and miss each other. Let's say, several threads start to search through a pile of data starting from different offsets. Somehen one thread has found the needle, now the other threads do not need to continue searching any more. How to tell them to cancel their search? This is not easy and therefore is a good simple example to show problems in asynchronous programming, but only a simple one. There are hell of problem situations of similar nature with asynchronous programming ...


On particular weakness of the pure actor models are that they always process messages in an unordered fashion. This is yet another of those cases where "Erlang doesn't implement the actor model". Not only can you select which messages to process in a given state. You also have a FIFO guarantee among pairs of processes. And this is crucial because it allows you to limit the state space of possible events that can occur.

As for the search-for-a-needle problem, you need either something like package "context" in Go, or links like in Erlang. You simply link together all the searchers. The first one to find a needle, sends off a message and then terminates. Because it is linked to the other processes, they also terminate. If two processes both find a needle at the same time, you have two answers, but you can do whatever you want with that in the receiver. Note that in Erlang, because of process isolation, abrupt termination is usually safe. If you have cleanup to do, you can always ask the process to turn the termination into a message, but then you are also obliged to periodically taste your mailbox to make sure there are no such message in it.

I agree that asynchronous messaging is harder than a single thread or synchronous messaging. But:

* It is necessary in a lot of cases.
* It is very often faster because there is a lot less contention going on. Especially in the latency department.
* Properly written, it tend to be more flexible.
* If it schedules preemptively, then async systems are more robust against a few rogue parts of the system taking too long time.



Anto Aravinth

unread,
Mar 10, 2018, 4:21:18 AM3/10/18
to golang-nuts
Thanks a lot, that was a solid information shared by everyone. 

Kaveh Shahbazian

unread,
Mar 10, 2018, 5:31:36 AM3/10/18
to golang-nuts
I had a related question asked on Elixir Forum:

Is handle_call part of the Actor Model? Since it sequentialize/synchronize things, is sound a bit like CSP (to me).

An answer provided by Joe Armstrong (one of Erlang inventors) states that indeed CSP influenced the design of Erlang and at the time, they were not familiar with Actor Model.
Reply all
Reply to author
Forward
0 new messages