The Art of Concurrent
Programming
Past, Present and Future
Iskren Chernev
What is this talk about
● What is parallel, concurrent, asynchronous
● Brief history
● Run Loops
● Callbacks
● Promises
● Coroutines
Parallel, Concurrent, Asynchronous
● Parallel - 2 pieces of code running at the same time (threads/processes
running different CPUs, cores)
● Concurrent - the begining and end of 2 pieces of code overlap, but not
necessarily run at the same time (could be parralel, but not necessarily). So it
appears to overlap, but it doesn’t have to
● Asynchronous - an operation that will complete at a later time, but its
execution doesn’t block the thread/process
● We’ll cover Asynchronous programming in this lecture
Brief overview of the environment
● Obviously this talk is opinionated, so here’s what I have in mind
● server-side business logic applications
● little amount of computation
● A lot of IO (talking to other (micro)-services and databases)
● A huge volume of “easy” requests
● Client side is also mostly event driven with little computation so most of talk
applies there too
Brief History
● Single Threaded software
● Mutli-core brought with it Multi Threaded Software
○ (+) Its very close to single threaded software
○ (-) slow context switch, creation
○ (-) cache trashing
○ (-) need for synchronization primitives -- this is complicated
● With Node.js (2009) came the hype around asynchronous, single threaded
programming
● After that most other popular languages added asynchronous primitives and
libraries as standard, or popularized and extended existing asynchronous
primitives (Java, C#, Objective-C, Python)
So what is this Node.js all about
var server = http.createServer((request, response) => {
response.end( 'It Works!! Path Hit: ' + request.url);
})
server.listen(PORT, () => {
console.log("Server listening on: http://localhost:%s" , PORT);
})
Node.js - basic
function doSomethingSync(args) {
r = doSlow1(args);
return doSlow2(r)
}
function doSomething(args, done) {
doSlow1(args, (err, res) => {
if (err) return done(err);
doSlow2(res, done);
});
}
● Easy function creation with “closures” is a must
● Every async computation calls a given function after finished
What else - let’s try an if
function doSomethingSync(args) {
if (cond) {
r = doSlow1();
} else {
r = 5;
}
doFast(r);
return doSlow2();
}
WTF
function doSomething(args, done) {
var doRest = (r, done) => {
doFast(r)
doSlow2(done)
}
if (cond) {
return doSlow1((err, res) => {
if (err) return done(err)
doRest(arg, done)
})
}
doRest(5, done);
}
Key takeaways from Node.js
● Its great because it showed the world
what asynchronous code looks like and
how to do it with callbacks
● It sucks to actually code it (there is
async.js but its still pretty painful)
● Is here a better way?
● (pro) its very fast
● (pro) all libraries support callback async
interface
● (cons) writing correct async-callback
code is tricky to do right (easy to
double call the callback, or not call it at
all, or forget to return when calling done
● (cons) and its ugly (deep nesting for a
chain of async operations)
Run Loops
● Something like a thread manager
● Operations a run loop should implement
○ schedule(fn) -- this function only is enough
to implement basic concurrent systems
with multithreaded blocking IO (main
thread)
○ scheduleLater(when, fn) -- similar to
schedule but executes at a later point
○ addSocketWithEvents(sock, evmask, fn)
- register for socket events
○ NOTE: that linux support for async DISK io
is spotty, and is mostly wrapped with
thread pool under the hood
● Run loops are the basis of asynchronous
programming
● You can have one or many, and
communicate between them using
schedule
● Node.js has a run-loop built in, but other
languages added those later
● Libraries should explicitly support run
loops (if there are many), and most “old”
languages, “old” libraries are blocking
● You could shim any blocking operation
with a thread pool and a single main
run-loop
What is a Promise
● Called in different ways : Future,
CompletionStage (Java), Task (C#) etc
● Its an object representing the result of
an asynchronous operation
● It supports chaining
function doSomethingSync(args) {
if (cond) {
r = doSlow1();
} else {
r = 5;
}
doFast(r);
return doSlow2();
}
function doSomething(args) {
if (cond) {
r = doSlow1();
} else {
r = 5;
}
Promise.resole(r).then((r) => {
doFast(r);
return doSlow2();
});
}
Promises -- loops
function doSomethingConc(n) {
var futures = []
for (var i = 0; i < n; ++i) {
futures.push(doSlow(i));
}
// returns a new future, which
// resolves to an array of results
return Promise.all(futures);
}
function doSomethingSeries(n) {
var future = Promise.resolved(null);
var every = (i) => {
future = future.then(() => doSlow(i));
}
for (var i = 0; i < n; ++i) {
every(i);
}
return future;
}
Promisses - notes
● Error propagation
● If you need to exit early from a chain you have to use exceptions
● Compared to callbacks -- promisses guarantee listener is called at most once
● So callbacks are still necessary if you need to call listener many times
● In some languages (C++, some JS libs), promise and future are separate object
● In typed languages (C++, Java, C#) you need a few different APIs on the promise to handle async
and sync returns
● Normally the listener is called right after its produced, on the same thread, but you can change
that
● Promises are hard to implement -- use an official/stable lib, do NOT do it yourself
Coroutines
● Coroutines are mostly syntax sugar on top
of promises, but it pays off!
● The observation is that mostly you have a
linear chain of promises, so in that case
you can use a keyword await (C#, python)
/ yield (python)
● It handles linear code, ifs and serial
loops
● You can always fall back to promises if
what you’re doing is more complicated
(branches off, parralel loops)
● To implement you need support in the
language
async def doStuff(x):
if cond:
r = async doSlow(x)
else:
r = 5
doFast(r)
return async doSlow()

The art of concurrent programming

  • 1.
    The Art ofConcurrent Programming Past, Present and Future Iskren Chernev
  • 2.
    What is thistalk about ● What is parallel, concurrent, asynchronous ● Brief history ● Run Loops ● Callbacks ● Promises ● Coroutines
  • 3.
    Parallel, Concurrent, Asynchronous ●Parallel - 2 pieces of code running at the same time (threads/processes running different CPUs, cores) ● Concurrent - the begining and end of 2 pieces of code overlap, but not necessarily run at the same time (could be parralel, but not necessarily). So it appears to overlap, but it doesn’t have to ● Asynchronous - an operation that will complete at a later time, but its execution doesn’t block the thread/process ● We’ll cover Asynchronous programming in this lecture
  • 4.
    Brief overview ofthe environment ● Obviously this talk is opinionated, so here’s what I have in mind ● server-side business logic applications ● little amount of computation ● A lot of IO (talking to other (micro)-services and databases) ● A huge volume of “easy” requests ● Client side is also mostly event driven with little computation so most of talk applies there too
  • 5.
    Brief History ● SingleThreaded software ● Mutli-core brought with it Multi Threaded Software ○ (+) Its very close to single threaded software ○ (-) slow context switch, creation ○ (-) cache trashing ○ (-) need for synchronization primitives -- this is complicated ● With Node.js (2009) came the hype around asynchronous, single threaded programming ● After that most other popular languages added asynchronous primitives and libraries as standard, or popularized and extended existing asynchronous primitives (Java, C#, Objective-C, Python)
  • 6.
    So what isthis Node.js all about var server = http.createServer((request, response) => { response.end( 'It Works!! Path Hit: ' + request.url); }) server.listen(PORT, () => { console.log("Server listening on: http://localhost:%s" , PORT); })
  • 7.
    Node.js - basic functiondoSomethingSync(args) { r = doSlow1(args); return doSlow2(r) } function doSomething(args, done) { doSlow1(args, (err, res) => { if (err) return done(err); doSlow2(res, done); }); } ● Easy function creation with “closures” is a must ● Every async computation calls a given function after finished
  • 8.
    What else -let’s try an if function doSomethingSync(args) { if (cond) { r = doSlow1(); } else { r = 5; } doFast(r); return doSlow2(); } WTF function doSomething(args, done) { var doRest = (r, done) => { doFast(r) doSlow2(done) } if (cond) { return doSlow1((err, res) => { if (err) return done(err) doRest(arg, done) }) } doRest(5, done); }
  • 9.
    Key takeaways fromNode.js ● Its great because it showed the world what asynchronous code looks like and how to do it with callbacks ● It sucks to actually code it (there is async.js but its still pretty painful) ● Is here a better way? ● (pro) its very fast ● (pro) all libraries support callback async interface ● (cons) writing correct async-callback code is tricky to do right (easy to double call the callback, or not call it at all, or forget to return when calling done ● (cons) and its ugly (deep nesting for a chain of async operations)
  • 10.
    Run Loops ● Somethinglike a thread manager ● Operations a run loop should implement ○ schedule(fn) -- this function only is enough to implement basic concurrent systems with multithreaded blocking IO (main thread) ○ scheduleLater(when, fn) -- similar to schedule but executes at a later point ○ addSocketWithEvents(sock, evmask, fn) - register for socket events ○ NOTE: that linux support for async DISK io is spotty, and is mostly wrapped with thread pool under the hood ● Run loops are the basis of asynchronous programming ● You can have one or many, and communicate between them using schedule ● Node.js has a run-loop built in, but other languages added those later ● Libraries should explicitly support run loops (if there are many), and most “old” languages, “old” libraries are blocking ● You could shim any blocking operation with a thread pool and a single main run-loop
  • 11.
    What is aPromise ● Called in different ways : Future, CompletionStage (Java), Task (C#) etc ● Its an object representing the result of an asynchronous operation ● It supports chaining function doSomethingSync(args) { if (cond) { r = doSlow1(); } else { r = 5; } doFast(r); return doSlow2(); } function doSomething(args) { if (cond) { r = doSlow1(); } else { r = 5; } Promise.resole(r).then((r) => { doFast(r); return doSlow2(); }); }
  • 12.
    Promises -- loops functiondoSomethingConc(n) { var futures = [] for (var i = 0; i < n; ++i) { futures.push(doSlow(i)); } // returns a new future, which // resolves to an array of results return Promise.all(futures); } function doSomethingSeries(n) { var future = Promise.resolved(null); var every = (i) => { future = future.then(() => doSlow(i)); } for (var i = 0; i < n; ++i) { every(i); } return future; }
  • 13.
    Promisses - notes ●Error propagation ● If you need to exit early from a chain you have to use exceptions ● Compared to callbacks -- promisses guarantee listener is called at most once ● So callbacks are still necessary if you need to call listener many times ● In some languages (C++, some JS libs), promise and future are separate object ● In typed languages (C++, Java, C#) you need a few different APIs on the promise to handle async and sync returns ● Normally the listener is called right after its produced, on the same thread, but you can change that ● Promises are hard to implement -- use an official/stable lib, do NOT do it yourself
  • 14.
    Coroutines ● Coroutines aremostly syntax sugar on top of promises, but it pays off! ● The observation is that mostly you have a linear chain of promises, so in that case you can use a keyword await (C#, python) / yield (python) ● It handles linear code, ifs and serial loops ● You can always fall back to promises if what you’re doing is more complicated (branches off, parralel loops) ● To implement you need support in the language async def doStuff(x): if cond: r = async doSlow(x) else: r = 5 doFast(r) return async doSlow()