Skip to main content

Concurrency & Async

Concurrency models are one of the biggest differentiators between programming languages. Here's how different languages handle asynchronous programming, threads, and concurrent execution.

Basic Async / Concurrent Execution​

// Promises
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve("data"), 1000);
});
};

// Async/await
async function main() {
const result = await fetchData();
console.log(result);
}

// Promise.all (parallel)
const [a, b] = await Promise.all([
fetchData(),
fetchData(),
]);

// Promise.race (first to complete)
const fastest = await Promise.race([
fetchData(),
fetchData(),
]);

Thread Safety & Synchronization​

// JavaScript is single-threaded (event loop)
// No shared memory issues in main thread

// Web Workers (browser) / Worker Threads (Node.js)
const { Worker } = require('worker_threads');

const worker = new Worker('./worker.js');
worker.postMessage({ data: 'hello' });
worker.on('message', (msg) => {
console.log('From worker:', msg);
});

// SharedArrayBuffer (shared memory)
const buffer = new SharedArrayBuffer(1024);
const view = new Int32Array(buffer);
Atomics.add(view, 0, 1); // Atomic operation

Key Takeaways​

  • Concurrency models vary widely -- Go uses goroutines with channels (CSP style): go func() { ch <- "hello" } and msg := <-ch. JavaScript, Python, Rust, C#, and Swift use async/await: async def fetch_data() in Python, async fn fetch_data() in Rust with tokio, async Task<string> FetchDataAsync() in C#. C, C++, and Java use traditional OS threads with pthread_create, std::thread, or Thread.start(), plus mutexes for synchronization. Goroutines are lightweight (millions possible); threads are heavier. Async/await suits I/O-bound work; threads suit CPU-bound parallelism. Choose Go for simple concurrency; choose async/await for I/O-heavy servers; choose threads when you need true parallelism and control.

  • Channel-based communication is first-class in Go -- Go's built-in channels and select make producer-consumer patterns idiomatic: ch := make(chan string), ch <- "msg", msg := <-ch, and select { case msg := <-ch1: ... case msg := <-ch2: ... }. Rust offers std::sync::mpsc::channel() and C# has Channel.CreateUnbounded<T>(), but they are library features. Go's design encourages "share memory by communicating" rather than mutexes. If you build pipelines, worker pools, or event-driven systems, Go's channels are the most ergonomic; Rust and C# provide similar power with more setup.

  • Go and Rust excel at concurrency -- Go's goroutines are lightweight (2KB stacks, multiplexed onto OS threads) and easy to spawn: go worker(id). Rust's ownership model prevents data races at compile time: the compiler rejects sharing mutable state across threads without synchronization. For example, Arc<Mutex<i32>> in Rust ensures safe shared access. Go relies on channels and discipline; Rust enforces safety statically. Choose Go when you want simple, fast concurrency with minimal friction; choose Rust when you need maximum safety and performance without a garbage collector.

  • Swift introduces actors -- Actors provide built-in thread-safe shared state by serializing access: actor Counter { private var count = 0; func increment() { count += 1 } } -- all methods run serially, so no data races. You call with await account.deposit(100). This replaces manual Mutex/Lock patterns. Swift's actors integrate with async/await and structured concurrency. If you build concurrent systems in Swift (e.g., iOS/macOS), prefer actors over manual locking; for other languages, use mutexes or channel-based designs.

  • JavaScript is single-threaded -- The event loop handles async via callbacks and Promises: await fetch('/api') does not block the thread; it yields until the response arrives. True parallelism requires Web Workers (browser) or Worker Threads (Node.js) with postMessage for communication. CPU-heavy work (hashing, image processing) blocks the main thread; offload it to workers. For I/O-bound Node.js servers, async/await is sufficient; for CPU-bound tasks, add workers or consider a multi-threaded language like Go or Rust.