public
Last active

Programming Challenge: Launch 4 threads, goroutines, coroutines, or whatever your language uses for concurrency, in addition to the main thread. In the first 3, add numbers together (see sample code below) and pass the results to the 4th thread. That 4th thread should receive the 3 results, add the numbers together, format the results as a string (see sample code), and pass the result back to `main` to be printed. Do this as succinctly and readably as possible. _Go!_ #golang #programming #concurrency #challenge

  • Download Gist
goroutines2.go
Go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
// Steve Phillips / elimisteve
// 2013.01.03
 
package main
 
import "fmt"
 
// intDoubler doubles the given int, then sends it through the given channel
func intDoubler(ch chan int, n int) {
ch <- n*2
}
 
func main() {
// Make channel of ints
ch := make(chan int)
answer := make(chan string)
 
// Spawn 3 goroutines (basically threads) to process data in background
go intDoubler(ch, 10)
go intDoubler(ch, 20)
go func(a, b int) { ch <- a+b }(30, 40) // Take 2 ints, write sum to `ch`
 
// Create anonymous function on the fly, launch as goroutine!
go func() {
// Save the 3 values passed through the channel as x, y, and z
x, y, z := <-ch, <-ch, <-ch
// Calculate answer, write to `answer` channel
answer <- fmt.Sprintf("%d + %d + %d = %d", x, y, z, x+y+z)
}()
 
// Print answer resulting from channel read
fmt.Printf("%s\n", <-answer)
}

Especially your line 26 is nearly impossible to get more beautifull and being just a shitty erlang programmer I only came up with something like that:

% Jeena Paradies
% 2013-01-03

-module (doubler).
-export ([start/0, int_doubler/2, collector/2]).

% int_doubler doubles the given int, then sends it to the given process
int_doubler(X, Collector) ->
    Collector ! X * 2.

% collector collects the answers untill it gets 3, formats them and sends
% the string to the main process
collector(Answers, Main) ->
    % wait for a message form one of the int_doubler or the Plus processes
    receive
        N ->
            case Answers of
                [X, Y] -> % if we have already 2 then this is the last
                    Result = io_lib:format("~w + ~w + ~w = ~w~n", [X, Y, N, X + Y + N]),
                    Main ! lists:flatten(Result);
                _ -> collector(Answers ++ [N], Main)
            end
    end.

start() ->
    % spawn one process to collect the data
    Collector = spawn(?MODULE, collector, [[], self()]),

    % spawn 3 processes to process data in background
    spawn(?MODULE, int_doubler, [10, Collector]),
    spawn(?MODULE, int_doubler, [20, Collector]),
    Plus = fun (A, B) -> Collector ! A + B end,
    spawn(fun() -> Plus(10, 22) end),

    % wait for a message from the collector process and print it
    receive
        Result -> io:format("~s", [Result])
    end.

I don't have the distinction between chanels and threads/goroutines, everything is just a process and every process can send messages to other processes. In the end I assume one would do stuff like that in a general way with OTP's gen_fsm or something similar.

More than slightly annoyed that authentication is required for gists these days... Anyway, here's the absolutely inelegant but short C variant of this.

/* gcc -std=c99 -fopenmp -Wall test.c -o test -lrt && ./test */
#include

int main(int argc, char *argv[]) {
int data[] = {10, 20, 30};
const int loops = sizeof(data)/sizeof(int);

#pragma omp parallel for schedule(dynamic, 1)
for (int i = 0; i < loops; i++)
{
if (i == loops - 1)
data[i] += 40;
else
data[i] *= 2;
}

printf("data: %i %i %i = %i\n", data[0], data[1], data[2],
       data[0] + data[1] + data[2]);
return 0;

}

Some haskell, perhaps? :)

import Control.Concurrent

intDoubler :: MVar Int -> Int -> IO ()
intDoubler v n = putMVar v (n*2)

intSum :: MVar Int -> MVar Int -> MVar Int -> MVar Int -> IO ()
intSum v1 v2 v3 s = do
  i1 <- takeMVar v1
  i2 <- takeMVar v2
  i3 <- takeMVar v3
  putMVar s (i1 + i2 + i3)

main :: IO ()
main = do
  v1 <- newEmptyMVar
  v2 <- newEmptyMVar
  v3 <- newEmptyMVar
  vs <- newEmptyMVar
  forkIO $ intDoubler v1 10
  forkIO $ intDoubler v2 20
  forkIO $ (\v3 i1 i2 -> putMVar v3 (i1 + i2)) v3 30 40
  forkIO $ intSum v1 v2 v3 vs
  result <- takeMVar vs
  print result

-- *Main> main 
-- 130

@anarazel I didn't know there were C libraries that auto-spawned threads... that's pretty awesome.

@jonte And yes, hard to say no to Haskell :-)

Thanks for the code samples, all! I'd still like to see some Scala and Clojure up in here...

My take with Clojure and the future construct.

  (let [x (future (* 2 10))
        y (future (* 2 20))
        z (future (+ 30 40))
        result (future (str @x " + " @y " + " @z " = " (+ @x @y @z)))]
    (println @result))

@danielsz Very elegant! Do futures automatically run concurrently in Clojure? And what about in parallel; are you using multiple cores (even though that wouldn't speed things up with the specific values we're using here)?

Hi Steve,

From the docs: A future takes a body of expressions and yields a future object that will invoke the body in another thread, and will cache the result and return it on all subsequent calls to deref/@.

Clojure runs on the JVM, where threads are native. I don't know if the JVM always distributes threads across multiple cores. A JVM expert should be able to shed some light.

Clojure has rich concurrency semantics, and I'm pretty sure there are other ways to achieve the same effect. I used futures because it seemed to me the appropriate construct for this exercise.

Just for the record, for those that prefer the printf idiom, you can do it like so when binding to result:

(future (format "%1$d + %2$d + %3$d = %4$d" @x @y @z (+ @x @y @z)))

Finally figured this out in Rust! Note: I'm a total Rust n00b, so it's extremely likely that this could be made more elegant. The repetition of chan.clone() is especially upsetting to my eyes, but it does the job:

extern mod std;
use task::spawn;
use pipes::{stream, Port, Chan, SharedChan};

// Purely functional doubler
// No need to combine computation / means of sending it in the same function
// In a real example, this could be an expensive computation
fn double(x: int) -> int {
    x * 2
}

fn main() {
    // Create port/channel pair for receiving/sending ints, respectively
    let (port, chan): (Port<int>, Chan<int>) = stream();
    // To allow multiple tasks to write, turn the channel into a SharedChan
    let chan = SharedChan(move chan);

    // Spawn 3 tasks to process data in the background,
    // each writing to its own clone of the SharedChan
    let child = chan.clone();
    do spawn |move child| { child.send(double(10)); }
    let child = chan.clone();
    do spawn |move child| { child.send(double(20)); }
    let child = chan.clone();
    do spawn |move child| { child.send((|a: int, b: int| a+b)(30, 40)); }

    // Make port/channel pair for the string result
    let (result_port, result_chan): (Port<~str>, Chan<~str>) = stream();
    do spawn |move port, move result_chan| {
        let (x, y, z) = (port.recv(), port.recv(), port.recv());
        let result: ~str = fmt!("%d + %d + %d = %d", x, y, z, x+y+z);
        result_chan.send(move result);
    }

    // Receive and print result
    io::println(result_port.recv());
}

Another Haskell solution, this one uses sparks and Control.Parallel

import Control.Parallel

main :: IO ()
main = putStrLn . show $ a `par` b `par` c `par` result `par` result
  where double = (2 *)
        a = double 10
        b = double 20
        c = 30 + 40
        result = a + b + c

And running it:

./parallel +RTS -N2 -sstderr
130
          55,188 bytes allocated in the heap
              20 bytes copied during GC
          45,692 bytes maximum residency (1 sample(s))
          23,940 bytes maximum slop
               2 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0         0 colls,     0 par    0.00s    0.00s     0.0000s    0.0000s
  Gen  1         1 colls,     0 par    0.00s    0.00s     0.0001s    0.0001s

  Parallel GC work balance: -nan% (serial 0%, perfect 100%)

  TASKS: 4 (1 bound, 3 peak workers (3 total), using -N2)

  SPARKS: 4 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 4 fizzled)

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    0.00s  (  0.00s elapsed)
  GC      time    0.00s  (  0.00s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    0.00s  (  0.00s elapsed)

  Alloc rate    596,150,106 bytes per MUT second

  Productivity  87.6% of total user, 211.7% of total elapsed

Hmm, the Haskell versions don't seem to output what they're supposed to, which is the string

20 + 40 + 70 = 130

with a newline. @elimisteve's Go, @jeena's Erlang, @danielsz's Clojure and my Rust all print it like this and @anarazel's C is close but also prints "data: " in front. However, @jonte and @ocharles, your Haskell versions seem to only print 130.

I don't mean to bash @jeena's nice Erlang code, but here is my quick take on it:

-module (foo).
-compile (export_all).

collect_3(Target, [X, Y, Z] = L) ->
    Target ! io_lib:format("~w + ~w + ~w = ~w~n", [Z, Y, X, lists:sum(L)]);
collect_3(Target, L) ->
    receive V -> collect_3(Target, [V | L]) end.

main() ->
    Collector = spawn(?MODULE, collect_3, [self(), []]),
    spawn(fun() -> Collector ! 10 * 2 end),
    spawn(fun() -> Collector ! 20 * 2 end),
    spawn(fun() -> Collector ! 30 + 40 end),
    receive X -> io:format(X) end.

Example:

$ erl
1> c(foo).
{ok,foo}
2> foo:main().
20 + 40 + 70 = 130
ok
3>

Here's another one with Clojure

(let [v (pmap #(eval %) '[(* 2 10)(* 2 20)(+ 30 40)])]
(println @(future (str (reduce #(str %1 "+" %2) v) "=" (reduce + v)))))

One line with bash!

(expr 10 \* 2 & expr 20 \* 2 & expr 30 \+ 40) | (read a; read b; read c; echo "\"$a + $b + $c = \"; $a + $b + $c" | bc)

@yoavrubin: the first line of your Clojure example can be made even more succint and clear:

(let [v (pvalues (* 2 10) (* 2 20) (+ 30 40))]

And using a function instead of let, it can also be made an one-liner:

(println @(future (#(str (apply str (interpose \+ %)) \= (apply + %)) (pvalues (* 2 10) (* 2 20) (+ 30 40)))))

When squeezed as much as possible (and loosing a clarity), it also fits into the one line of a gist comment:

(print@(future(#(str(apply str(interpose\+%))\=(apply +%))(pvalues(* 2 10)(* 2 20)(+ 30 40)))))
using System;
using System.Linq;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(
            Task.WhenAll(
                Task.Run(() => 10 * 2), Task.Run(() => 20 * 2), Task.Run(() => 30 + 40))
            .ContinueWith(t => string.Join(" + ", t.Result) + " = " + t.Result.Sum()).Result
        );
    }
}

Awesome guys, these are great.

@nizox Other than using ampersands to background processes, I didn't know it was so easy to do asynchronous programming at the command line. Sweet.

@aeonshifter I can see why using C# is much nicer than Java.

Looks like I'm gonna have to step up my game and make my Go example more succinct...

package main

import "fmt"

func main() {
    ch := make(chan int)
    answer := make(chan string)

    intDoubler := func(n int) { ch <- n*2 }
    go intDoubler(10)
    go intDoubler(20)
    go func(a, b int) { ch <- a+b }(30, 40)

    go func() {
        x, y, z := <-ch, <-ch, <-ch
        answer <- fmt.Sprintf("%d + %d + %d = %d", x, y, z, x+y+z)
    }()

    fmt.Printf("%s\n", <-answer)
}

Bending the rules a bit, similar to other examples...

package main

func main() {
    ch := make(chan int)

    go func(a int)    { ch <- a*2 }(10)
    go func(a int)    { ch <- a*2 }(20)
    go func(a, b int) { ch <- a+b }(30, 40)

    x, y, z := <-ch, <-ch, <-ch
    println(x, "+", y, "+", z, "=", x+y+z)
}

I'm noticing a distinct lack of python here. :P

from concurrent.futures import Future, ThreadPoolExecutor

def fmt_sum(*int_futures):
    ints = tuple(map(Future.result, int_futures))
    return ' + '.join(map(str, ints)) + ' = ' + str(sum(ints))

with ThreadPoolExecutor(max_workers=4) as tp:
    a = tp.submit(lambda x: x * 2, 10)
    b = tp.submit(lambda x: x * 2, 20)
    c = tp.submit(lambda x, y: x + y, 30, 40)
    d = tp.submit(fmt_sum, a, b, c)

print(d.result())

Here's an update of the Rust solution:

use task::spawn;

fn main() {
    let (port, chan) = pipes::stream();
    let chan = pipes::SharedChan(chan);

    let (s1, s2, s3) = (chan.clone(), chan.clone(), chan.clone());
    do spawn { s1.send( (|a: int| a*2)(10) ) }
    do spawn { s2.send( (|a: int| a*2)(20) ) }
    do spawn { s3.send( (|a: int, b: int| a+b)(30, 40) ) }

    let (x, y, z) = (port.recv(), port.recv(), port.recv());
    io::println(fmt!("%d + %d + %d = %d", x, y, z, x+y+z));
}

Oh hey, turns out Rust has a futures library after all:

extern mod std;
use std::future::spawn;

fn main() {
    let p1 = do spawn { (|a: int| a*2)(10) },
        p2 = do spawn { (|a: int| a*2)(20) },
        p3 = do spawn { (|a: int, b: int| a+b)(30, 40) };

    let (x, y, z) = (p1.get(), p2.get(), p3.get());
    io::println(fmt!("%d + %d + %d = %d", x, y, z, x+y+z));
}

A shorter (but still readable) Haskell solution using ForkIO. Also, formatted as requested.

import Control.Concurrent
import Data.List (intercalate)

main = do
  ch <- newChan
  answer <- newEmptyMVar
  mapM (\e -> forkIO $ seq e $ writeChan ch e)
       [10 * 2, 20 * 2, 30 + 40]
  forkIO $ do nums <- mapM readChan [ch,ch,ch]
              let ans = intercalate " + " (map show nums)
                        ++ " = " ++ show (sum nums)
              seq ans $ putMVar answer ans
  putStrLn =<< takeMVar answer

Haskell's laziness has to be carefully considered to ensure that the multiplications & additions run on the right thread. forkIO isn't really designed for scheduling pure computations onto multiple threads; par is probably a better choice here. I checked the above using Debug.Trace and myThreadId, and it seems to do the right thing.

Ruby (works in MRI, RBX and Jruby):

require "rubygems"
require "celluloid"

ws = []
times_two = lambda { |x| x * 2 }
ws << Celluloid::Future.new { times_two.call(10) }
ws << Celluloid::Future.new { times_two.call(20) }
ws << Celluloid::Future.new { lambda { |x,y| x + y }.call(30, 40) }
r = Celluloid::Future.new do
  vs = ws.map { |w| w.value }
  "#{vs.join(" + ")} = #{vs.reduce(:+)}"
end

puts r.value

Slight variation on Yoav (excellent) Clojure solution, replacing the eval with pcalls to functions.

(let [v (pcalls #(* 2 10) #(* 2 20) #(+ 30 40))] 
   (println @(future (str (reduce #(str %1 "+" %2) v) "=" (reduce + v)))))

Just notice @mnicky had even better solution with pvalues...

Mozart/Oz:

functor
import
    Application System
define X Y Z S in
    thread X =  2 * 10 end
    thread Y =  2 * 20 end
    thread Z = 30 + 40 end

    thread S = X#" + "#Y#" + "#Z#" = "#(X + Y + Z) end

    {System.showInfo S}
    {Application.exit 0}
end

Another Clojure example, this time with a bit of macro meta-programming to give us a beautiful syntax:

(defmacro go [& body] `@(future ~@body))

(go (+ (go (* 2 10)) 
       (go (* 2 20))
       (go (+ 30 40))))

You're really missing a Scala example.

import concurrent.Future
  for (a <- Future { 2 * 10 };
       b <- Future { 2 * 20 };
       c <- Future { 30 + 40 };
       sum <- Future { a + b + c })
  { println(s"$a + $b + $c = $sum") }

That's pretty readable.

And the console output:

scala> :paste
// Entering paste mode (ctrl-D to finish)

  for (a <- Future { 2 * 10 };
        b <- Future { 2 * 20 };
        c <- Future { 30 + 40 };
        sum <- Future { a + b + c })
  { println(s"$a + $b + $c = $sum") }

// Exiting paste mode, now interpreting.


scala> 20 + 40 + 70 = 130

C, written in an archaic style, likely would work on an early UNIX system.

#include <sys/wait.h>

#include <stdio.h>
#include <unistd.h>

int intDoubler(i)
     int i;
{
        return i * 2;
}

int intAdder2(i, j)
     int i, j;
{
        return i + j;
}

int intAdder3(i, j, k)
     int i, j, k;
{
        return i + j + k;
}

int main(argc, argv)
     int argc;
     char *argv[];
{
        int answer;
        int i, j, k;

        switch (fork()) {
        case 0:
                switch (fork()) {
                case 0:
                        return intDoubler(10);
                default:
                        break;
                }

                switch (fork()) {
                case 0:
                        return intDoubler(20);
                default:
                        break;
                }

                switch (fork()) {
                case 0:
                        return intAdder2(30, 40);
                default:
                        break;
                }

                wait(&i);
                wait(&j);
                wait(&k);
                return intAdder3(WEXITSTATUS(i), WEXITSTATUS(j), WEXITSTATUS(k));
        default:
                wait(&answer);
                printf("%d\n", WEXITSTATUS(answer));
                break;
        }

        return 0;
}

Here is nearly verbatim copy of the go source into haskell:

import Control.Monad
import Control.Concurrent
import Control.Concurrent.Chan

import Text.Printf

double :: Chan Int -> Int -> IO ()
double ch n = writeChan ch (n*2)

main = do
    ch <- newChan
    answer <- newChan

    forkIO $ double ch 10
    forkIO $ double ch 20
    forkIO $ (\a b -> writeChan ch (a + b)) 30 40
    forkIO $ do
        [x, y, z] <- replicateM 3 $ readChan ch
        writeChan answer $ (printf "%d + %d + %d = %d" x y z (x+y+z) :: String)

    printf "%s\n" =<< readChan answer

Haskell, using monad-par:

module Main where

import Control.Monad
import Control.Monad.Par
import Text.Printf

-- | some common boilerplate
defer :: (NFData a) => Par a -> Par (Par a)
defer = liftM get . spawn
deferP :: (NFData a) => a -> Par (Par a)
deferP = liftM get . spawnP

calc :: Par String
calc = do
  pa <- deferP (2 * 10 :: Int)
  pb <- deferP (2 * 20)
  pc <- deferP (30 + 40)
  -- pc <- defer $ liftM2 (+) pa pb  -- this is a mildly more exiting use case
  liftM3 summary pa pb pc
  where
    summary a b c = printf "%d + %d + %d = %d" a b c (a+b+c)

main = do
  print (runPar calc)

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.