Concurrency vs. concurrency

822 views
Skip to first unread message

Øyvind Teig

unread,
Jul 31, 2013, 6:24:45 AM7/31/13
to golan...@googlegroups.com
I saw Herb Sutter's lecture "C++ Concurrency, 2012 State of the Art (and Standard)" at C++ and Beyond [1] - after having seen Rob Pike's lecture "Go concurrency patterns" [2] last year. I was surprised to learn how strongly Sutter warned against blocking. I have tried to understand why and tried to discuss this in a new blog (that may be modified as the result of what kind of feedback I see here). It's at http://www.teigfam.net/oyvind/home/technology/072-pike-sutter-concurrency-vs-concurrency/. (Disclaimer: my blogs are without any ads of any sort, no money involved, and I am not affiliated with any of the parties)

Øyvind Teig

Alexei Sholik

unread,
Jul 31, 2013, 8:26:50 AM7/31/13
to Øyvind Teig, golang-nuts
Hi,

This is an interesting comparison and a very in-depth one. I haven't read all of it, but I noticed one thing that didn't seem right:

One of the most successful models for providing high-level linguistic support for concurrency comes from Hoare’s Communicating Sequential Processes, or CSP. Occam and Erlang are two well known languages that stem from CSP. 

CSP and Actor model are two different models of concurrency, from what I can tell. Erlang clearly implements the Actor model, all message passing is asynchronous by default. I don't know much about occam, Wikipedia article says it implements CSP and block on every send and receive.

You didn't compare Erlang to CSP directly, so I'm not saying your statement is incorrect, but I thought the distinction in communication semantics was worth pointing out.


--
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/groups/opt_out.
 
 



--
Best regards
Alexei Sholik

mortdeus

unread,
Jul 31, 2013, 9:01:15 AM7/31/13
to golan...@googlegroups.com
1. How does your xchan proposal differ from the actor model. As far as I can tell by reading your proposal an xchan is an actor.

2. Herb Sutter's opinion that ALL concurrent systems should be async is nonsense. I say that of course because go and plan9's thread model implements synchronous concurrency in a way that doesnt suck. The actor model has many setbacks of its own and by no means is an asynchronous system model guaranteed to produce a more efficient, responsive concurrent program than a program using a synchronous system model .  

3. The reality is that you have to design your program so that it effectively exploits the asynchronous nature of the  actor model. Just like you have to design synchronous concurrent  systems so there isn't heavy lock contention. Anytime we observe the efficiency of modern concurrent system or theoretical model,  we always find that the most devastating deficiency in a concurrent system is the developer inability to designing a correct, efficient, simple, scalable system with the tools he is currently provided.

Rob Pike in his talk "Concurrency isn't Parallelism" points this out by suggesting that the most fundamentally damning problem with concurrency is that we don't have an accurate understanding of what concurrency actually is in its fundamentally purest form. Rob suggests that the best way to understand concurrency is to view it as a foundational principle of software design; rather than a 
 bunch of low level, complex, and exploitable features we can add-on to our program. (multiple cores, hyper threading, GPGPU,  pthreads, actors, etc).

Therefore taking our new understanding of concurrency into consideration, it should become obviously clear that "concurrency vs concurrency" is irrelevant because you wont learn anything useful when you compare a method against a mindset.    
                   

Øyvind Teig

unread,
Jul 31, 2013, 1:41:12 PM7/31/13
to golan...@googlegroups.com, Øyvind Teig
This is a quote from Go official documents, [17] in the blog.

Øyvind Teig

unread,
Jul 31, 2013, 2:15:32 PM7/31/13
to golan...@googlegroups.com


kl. 15:01:15 UTC+2 onsdag 31. juli 2013 skrev mortdeus følgende:
1. How does your xchan proposal differ from the actor model. As far as I can tell by reading your proposal an xchan is an actor.

I have from time to time tried to study the actor model. I have no experience with it and don't know it I must admit. I've been looking at Wikipedia now. I see that π-calculus has a relationship with the actor model and occam-π (oocam-pi) has π-calculus added to CSP. My paper about XCHAN from last years' CPA-2012 conference was peer reviewed by the core of the occam-π people. I can not remember or find in my own files any mention from them about XCHAN being an actor. There may have been some mention in the question session, but that is in case faint to me. 

The XCHAN is no own process, and cannot take primary initiative. It cannot spawn processes. It is a channel with an added feedback channel. And sending never blocks at link level etc. See in my blog and paper. If XCHAN is an actor it must be a tiny actor! 

I'd be glad for any one who has studied the XCHAN paper and is an actor expert to tell me. Maybe you?

2. Herb Sutter's opinion that ALL concurrent systems should be async is nonsense. I say that of course because go and plan9's thread model implements synchronous concurrency in a way that doesnt suck. The actor model has many setbacks of its own and by no means is an asynchronous system model guaranteed to produce a more efficient, responsive concurrent program than a program using a synchronous system model . 

Sutter doesn't state that all should be asynchronous. Most may be seen as nonsense from some angle. But I must admit, I wouldn't have written the blog if I didn't wonder about his angle of view. And, totally blind as I may be, I don't see XCHAN having many setbacks..

3. The reality is that you have to design your program so that it effectively exploits the asynchronous nature of the  actor model. Just like you have to design synchronous concurrent  systems so there isn't heavy lock contention. Anytime we observe the efficiency of modern concurrent system or theoretical model,  we always find that the most devastating deficiency in a concurrent system is the developer inability to designing a correct, efficient, simple, scalable system with the tools he is currently provided.

Yes, but in the direction from "difficult" to "concurrency is easy". With the occam version it is quite easy because the language is so simple. occam-pi is more complex. I have some Go code from golang-nuts in the XCHAN paper, and Go is more complex, without parallel usage rules. But it's still "easier than most" I believe.

Rob Pike in his talk "Concurrency isn't Parallelism" points this out by suggesting that the most fundamentally damning problem with concurrency is that we don't have an accurate understanding of what concurrency actually is in its fundamentally purest form. Rob suggests that the best way to understand concurrency is to view it as a foundational principle of software design; rather than a 
 bunch of low level, complex, and exploitable features we can add-on to our program. (multiple cores, hyper threading, GPGPU,  pthreads, actors, etc).

In occam, every statement/line is a process! You have to specify if they run SEQ or PAR. Taken from there I believe Rob Pike is right. But Go by design meets the wall, not with multicore with shared memory, but if memory is not shared. Not so for occam. 

Therefore taking our new understanding of concurrency into consideration, it should become obviously clear that "concurrency vs concurrency" is irrelevant because you wont learn anything useful when you compare a method against a mindset.  

I like it, even if I don't follow you fully. They both have dimensions of method and mindset. The C++ people do have a difficult job in trying to set up a concurrent mindset. People who will be going to use futures must of course also have a mindset. Even features need mindsets.                    

Bryan Mills

unread,
Aug 1, 2013, 5:40:43 PM8/1/13
to golan...@googlegroups.com
Sutter has a decent point about futures being useful, but his extrapolation from "futures are useful" to "APIs should return futures instead of blocking" is utter nonsense.

Only the caller knows whether the caller actually has useful work to switch to in the meantime.  To take the example from Sutter's slide:

    auto result = async([=] { return f(x, y, z); });
    // ... code here runs concurrently with f...
    result.then([](int r) { go_and_use(r); });

is equivalent to (and uses the same number of threads and context-switches as!):

    auto other_stuff = async([] {
      // ... code here runs concurrently with f...
    });
    go_and_use(f(x, y, z));
    other_stuff.wait();

The latter version:
* avoids needless copying (and thus needless pollution of data cache),
* avoids needlessly transferring data between cores (a performance blunder that Sutter himself warns against in the discussion of read-write locks!)
* has a more readable control flow, and
* may occupy an additional thread stack while waiting for f() to return.

The slide at 1:00:00 is telling: the "Done" operation, which happens after all of the "wait()" calls, occurs before the tasks are actually completed.  That behavior is a very common class of _bug_ in aggressively asynchronous programs, and Sutter spends the entire rest of the talk describing a huge amount of boilerplate one can use to work around it.

Why not avoid that bug in the first place by using a runtime with decently lightweight threads (i.e. goroutines) and moving the concurrency to the caller side?

Leave internal concurrency in an API as the implementation detail it ought to be.  Provide blocking APIs, and let your callers add asynchronicity explicitly _when they will actually benefit from it_.

adnaan badr

unread,
Aug 1, 2013, 7:29:28 PM8/1/13
to golan...@googlegroups.com
Parts of this article are good enough to be included in the go doc for better understanding of concurrency. Maybe a third/simpler perspective could have been discussed w.r.t Unix pipes. I think they represent the sender receiver model quite closely. 

Bienlein

unread,
Aug 2, 2013, 4:38:04 AM8/2/13
to golan...@googlegroups.com
I don't know whether Rob Pike said this in the same presentation as mentioned here. But I listened to one where he says that race conditions and deadlocks happen much less when using chanels as opposed to mutexes, locks, or semaphores. I think this is the point. Channels are not a silver bullet with which you no longer run into deadlocks. Rather they are a means to get around mutexes, locks, or semaphores still being on the level of system programming.

Actors are something you can implement with the use of channels and Go routines very easily. With actors you still have deadlock potential, though. Deadlocks can't happen because of locking since there aren't any locks, but deadlocks can happen in the message flow. The missing message problem is a famous one here and it is exactly the same problem as when a Go routine sits on a channel, because of an error in the logic some item does not get added to the channel.

-- Bienlein

Luke Mauldin

unread,
Aug 2, 2013, 7:16:36 AM8/2/13
to golan...@googlegroups.com
Can anyone link to a simple golang.org/play examples that illustrates the scenario Bienlein is describing?

Luke

Jesse McNelis

unread,
Aug 2, 2013, 7:38:42 AM8/2/13
to Luke Mauldin, golang-nuts
On Fri, Aug 2, 2013 at 9:16 PM, Luke Mauldin <lukem...@gmail.com> wrote:
Can anyone link to a simple golang.org/play examples that illustrates the scenario Bienlein is describing?

In this case the function exits early due to detecting an error and never sends a value to the channel so the program deadlocks.

  
--
=====================
http://jessta.id.au

Øyvind Teig

unread,
Aug 2, 2013, 10:18:59 AM8/2/13
to golan...@googlegroups.com
The situation you describe here is very interesting. I assume that when a language does not have a concurrent process as first class citizen, spawning and joining may be difficult to get correct without deadlock etc. It's also about when to join, if the spawning process continues or blocks until all spawned processes have stopped, like occam does. Go often seems to wait for a (shared) join channel to simulate the same. The C++11 examples show that hidden solutions are difficult to discover, and must be learned, in the negative sense.

Robert Johnstone

unread,
Aug 2, 2013, 10:51:48 AM8/2/13
to golan...@googlegroups.com
Your second bit is correct, only the caller knows whether there is work to be done in the meantime, but you've got the reasoning backwards.  The API needs to accommodate clients that both do and do not have work to perform, so the safe option is to provide a future (or to provide both).  The reasoning *may* be different between C/C++ and Go, since in Go it is relatively easy to wrap a synchronous call in a goroutine, but it would still be several lines to do properly.  Finally, your concerns appear to be about micro-optimizations, which I doubt would be significant in the case of a blocking operation (i.e. waiting for some type of IO).

Øyvind Teig

unread,
Aug 2, 2013, 12:51:04 PM8/2/13
to golan...@googlegroups.com
Concurrency means potential deadlock. Channels (zero buffered or n-dim buffered), asynchronous systems (with infinite buffer size) may deadlock. The first could be seen at link level, the last at application level. Any type of conversation could potentially deadlock when a chain of dependency ends up waiting for each other. It simply means that the parties will freeze, and over some time other parts dependent on a deadlocked group will also deadlock, and the whole sea freezes. The deadlock could, in a large system sit there for years because their service was not requested. There is no "race conditions and deadlocks happen much less when using channels as opposed to mutexes, locks, or semaphores" with adjectives like 'much' and 'less' - if it means that you can take it more or less easy. Learn deadlock free patterns like [1]. Learn to see roles like master and slave, client and server. And the server for some could itself be a client using other servers. A process could be a server for a set of clients and that pattern could be deadlock-free, provided the interprocess protocol is 100% adhered to. If a server shall always respond, with values or error, it cannot, because of some if-then-else "forget" to respond as promised. When you then discover that some very seldom times a server does not respond (and you should log it as an error to be fixed) and you "solve" it as a client by either wait for the response or time out - the timeout may (or will) get you in deep shit. This is thinking that's ok and even appropriate at lower OSI levels to do protocol layering between machines, but could cause a plane to crash if used between processes. What will happen if the server sends a response just after the client timed out, and the client wants a new service from the same client? Understand these scenarios. Draw process data flow diagrams and learn to discover cycles. Model your implementation and learn Promela and use Spin to see if there is any deadlock there. Have a look at LTSA or PAT tools. Aliasing may be wanted (double linked lists), but deadlock is never wanted. Channels will in no way be "better" than mutexes since there is no compare operator between them. It's not even certain which is the "highest" abstraction level. But channels may deadlock, so you must learn to use them. Don't blame the channels if you deadlock. That would be as wrong as blaming the number 4 for representing four of something. If you don't take this seriously you may in a year become frustrated with channels that seemed to work fine when you "tested" but then still deadlocked afterwards. "But didn't you TEST?" That's deadlocks for you, they often don't shown up before you are in or on the air. I think channels are good tools for building concurrent systems. I have used them for 25 years and have only used mutexes or critical sections or locks when I did not have channels.

One more thing. Go channels and occam channels have somewhat different semantics. The languages are different implementations of CSP (when it comes to concurrency), so their fault scenarios would differ. But they would agree on deadlock. Beware.

[1] Knock-come when both need to start an initiative: http://oyvteig.blogspot.dk/2009/03/009-knock-come-deadlock-free-pattern.html

Bryan Mills

unread,
Aug 3, 2013, 12:11:27 AM8/3/13
to Robert Johnstone, golan...@googlegroups.com

My main concern is that blocking code is easier to read and understand than nonblocking code.  Paying the complexity price of concurrency when it isn't useful obfuscates code for no real purpose.

Nonblocking callers can easily spawn new futures as needed by making explicit "async" calls.  But blocking callers can only synchronize if the asynchronous code properly plumbs through a joinable future or return channel - and, as Sutter's  slides demonstrate, it's easy to implement that plumbing incorrectly.

--
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/Q8Mz2oR_UZQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages