Concurrency & Parallelism

(D) Overview

JS

The best way to describe Parallelism in Javascript is with this quote by Felix Geisendörfer:

Well, in node everything runs in parallel, except your code.

So while your Javascript runtime may use multiple threads for IO, your own code is getting run just by one. That's just how the evented model works. Different Javascript runtimes offer some options for concurrency or parallelism: NodeJS offers clustering, and Browsers offer web workers.

Go

On the other hand, Go is all about a concurrency which enables parallelism. It offers Goroutines which enables functions to run concurrently, and channels to communicate between them. While Go standard library has the "sync" package for synchronization primitives, it encourages more the use of Goroutines and channels, summarized as:

Do not communicate by sharing memory; instead, share memory by communicating

More on this subject:

(D) Async vs Sync APIs

JS

JS promotes writing async APIs, since sync APIs always block the caller, e.g:

const fs = require('fs');

// The caller to this function will be blocked while the file is being read.
function fetchA() {
   return fs.readFileSync('a.txt');
}

In the example above, the async fs.readFile() would be a better choice in most scenarios, making fetchA() async and unblocking its caller.

Go

On the other hand, Go promotes the sync APIs (see “Avoid concurrency in your API” in https://talks.golang.org/2013/bestpractices.slide#26) The reasoning behind this is that it is completely up to the caller's choice to be blocked or not by a sync API. Consider the following type definition and sync fetchA function:

type fetchResult struct {
    Message string
    Error   error
}

func fetchA() fetchResult {
    time.Sleep(time.Second * 4)
    return fetchResult{"A data", nil}
}

If the caller wants to be blocked, then he can just call the function

a := fetchA()

If the caller does not want to be blocked, then he could call the function inside a goroutine:

aChan := make(chan fetchResult, 0)
go func(c chan fetchResult) {
    c <- fetchA()
}(aChan)

// Do other things, and then read from channel
a := <-aChan    

(D) Sequential and Concurrent Patterns

JS

Even without parallelism, we can structure Javascript code in both sequential and concurrent flows. For the following exmaples, let’s assume fetchA(), fetchB() and fetchC() are all async functions returning a promise.

Sequential

function fetchSequential() {
    fetchA().then(a => {
        console.log(a);
        return fetchB();
    }).then(b => {
        console.log(b);
        return fetchC();
    }).then(c => {
        console.log(c);
    })
}

or

async function fetchSequential() {
    const a = await fetchA();
    console.log(a);
    const b = await fetchB();
    console.log(b);
    const c = await fetchC();
    console.log(c);
}

Concurrent

function fetchConcurrent() {
    Promise.all([fetchA(), fetchB(), fetchC()]).then(values => {
        console.log(values);
    }
}

or

async function fetchConcurrent() {
    const values = await Promise.all([fetchA(), fetchB(), fetchC()])
    console.log(values);
}

Go

For the following examples, assume fetchB() and fetchC() are defined as a sync function similarly to fetchA in the previous section (The full example is available at https://play.golang.org/p/2BVwtos4-j)

Sequential

func fetchSequential() {
    a := fetchA()
    fmt.Println(a)
    b := fetchB()
    fmt.Println(b)
    c := fetchC()
    fmt.Println(c)
}

Concurrent

func fetchConcurrent() {
    aChan := make(chan fetchResult, 0)
    go func(c chan fetchResult) {
        c <- fetchA()
    }(aChan)
    bChan := make(chan fetchResult, 0)
    go func(c chan fetchResult) {
        c <- fetchB()
    }(bChan)
    cChan := make(chan fetchResult, 0)
    go func(c chan fetchResult) {
        c <- fetchC()
    }(cChan)

    // order doesn't really matter!
    a := <-aChan
    b := <-bChan
    c := <-cChan
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}

or


func fetchConcurrent() {
    aChan := make(chan fetchResult, 0)
    bChan := make(chan fetchResult, 0)
    cChan := make(chan fetchResult, 0)

    go func(c chan fetchResult) {
        c <- fetchA()
    }(aChan)
    go func(c chan fetchResult) {
        c <- fetchB()
    }(bChan)
    go func(c chan fetchResult) {
        c <- fetchC()
    }(cChan)

    for i := 0; i < 3; i++ {
        select {
        case a := <-aChan:
            fmt.Println(a)
        case b := <-bChan:
            fmt.Println(b)
        case c := <-cChan:
            fmt.Println(c)

        }
    }
}

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8