layout: post title: “Concurrency Models: Python vs Elixir vs C# vs GO” date: 2022-10-03 00:18:23 +0700 updated: 2026-06-21 00:00:00 +0700 categories: [concurrency, python, elixir, c#, go, golang] —
Updated 2026: Added comprehensive GO (golang) section with 4-language comparisons and performance benchmarks.
Parallelism
When two or more processes can run at the exact same time. This can happen, for instance, in multicore systems, where each process runs on a different processor.
Concurrency
When two or more processes try to run at the same time on top of the same processor. This is usually solved by techniques such as time slicing.
However, these techniques do not execute in a truly parallel fashion. It just looks parallel to observers because of the speed at which the processor switches between tasks.
The operative system will take care of scheduling time with the processor for each process that requires it. Then, it’ll switch context between them, giving each one a slice of time.
No matter of the order of execution, the outcome shall be the same
| CPU Scheduling | an operating system module selecting next jobs and processes to run(uses context switch to stop and resume the context of a running thread or process) |
| GIL - Python Global Interpreter Lock | a mutex (or a lock) that allows only one thread to hold the control of the Python interpreter at a time. |
Elixir vs Python vs C# vs GO
Running concurrent processes in Elixir, Python, C#, and GO can have significant differences in terms of performance, syntax, and ease of use. Here are some key considerations:
Performance:
- Elixir is designed to be highly concurrent and can handle large numbers of processes efficiently on the BEAM virtual machine.
- GO (golang) excels at concurrent I/O with its lightweight goroutines and built-in channels, providing near-native performance.
- Python supports concurrency through threads (limited by GIL) and async/await, performing well for I/O-bound tasks.
- C# offers good performance with modern async/await and Task-based concurrency with full parallelism on multi-core systems.
Syntax:
- Elixir uses a functional programming paradigm with its own process syntax.
- GO provides simple syntax with goroutines (
go func()) and synchronous-looking channels - “Don’t communicate by sharing memory; share memory by communicating.” - Python and C# have familiar syntax and built-in support for threads and async/await.
Ease of use:
- GO’s simplicity makes it extremely approachable for concurrent programming - goroutines are easy to spawn and channels are intuitive.
- Python is easy for I/O concurrency but GIL creates complexity for CPU-bound tasks.
- Elixir’s functional paradigm has a steeper learning curve but excel at massive concurrency.
- C# provides a good balance with familiar syntax and strong tooling support.
Use Cases
Adding multiprocessing support in python comes with an overhead. Therefore the use cases have to be justified for the introduced complexity.
| Multiprocessing - CPU Intensive Tasks | string operations, search algo, graphics processing, number crunching, algo… |
| Async Programming - IO Intensive tasks | pass the task to another Actor, get a callback once it is done; Ex: database read/writes, web service calls, copy, download, upload data to disk or a network, .. |
Thread vs Process
| Process | is an instance of a computer program that is being executed. Any process has 3 basic components: an executable program, the associated data needed by the program (variables, work space, buffers, etc.) and the execution context of the program (State of process) |
| Thread | is an entity within a process that can be scheduled for execution; it is the smallest unit of processing that can be performed in an OS (Operating System) |
Threading (Python)
Module: threading (available since Python 1.5+)
Threads are lightweight compared to processes and share the same memory space. However, Python threads are affected by the Global Interpreter Lock (GIL), which means only one thread can execute Python bytecode at a time.
Thread Lifecycle: NEW → READY → BLOCKED → RUNNING → TERMINATED
Key characteristics:
- Shared memory and OS resources (open files, network connections)
- Lower overhead compared to multiprocessing
- Subject to thread interference and race conditions
Challenges:
- Race conditions - multiple threads accessing/modifying shared data without synchronization
- Deadlocks - threads waiting indefinitely for each other to release resources
Thread Synchronization:
- Lock: Ensures only one thread accesses a resource at a time
lock = threading.Lock() with lock: # Recommended approach # critical section pass - RLock (Reentrant Lock): Allows the same thread to acquire the lock multiple times
- Semaphore: Controls access to a shared resource through a counter
- Event: Allows threads to wait until a specific condition is met
Multiprocessing (Python)
Module: multiprocessing (available since Python 2.6+)
Multiprocessing bypasses the GIL by using separate Python processes instead of threads. Each process has its own Python interpreter and memory space.
Key characteristics:
- Each process is independent with its own memory space
- True parallelism on multi-core systems
- Higher overhead compared to threading due to process creation
- Inter-process communication (IPC) requires serialization/pickling
When to use:
- CPU-intensive tasks (calculations, data processing)
- When you need true parallelism without GIL limitations
- Tasks that can be easily divided among multiple processes
Example:
from multiprocessing import Pool
def cpu_intensive_task(n):
return n * n
if __name__ == '__main__':
with Pool(4) as p:
results = p.map(cpu_intensive_task, range(10))
Abstract Concurrency (Python)
Module: concurrent.futures (available since Python 3.2+)
Provides a high-level interface for asynchronously executing callables. This module abstracts away the complexity of managing threads or processes directly.
Key features:
- ThreadPoolExecutor: Uses a pool of threads for I/O-bound tasks
- ProcessPoolExecutor: Uses a pool of processes for CPU-bound tasks
- Futures: Represent the eventual result of an asynchronous operation
- Automatic resource management
Example:
from concurrent.futures import ThreadPoolExecutor, as_completed
def io_task(url):
# fetch from url
return result
with ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(io_task, url): url for url in urls}
for future in as_completed(futures):
result = future.result()
Asynchronous Programming (Python)
Module: asyncio (available since Python 3.4+)
Asyncio provides a framework for writing single-threaded concurrent code using coroutines. It’s ideal for I/O-bound operations and allows many concurrent operations on a single thread using event loops.
Key concepts:
- Coroutines: Functions defined with
async defthat can be paused and resumed - Tasks: Coroutines wrapped in a Task object scheduled on the event loop
- Event loop: Runs asynchronous tasks and manages their execution
- Awaitables: Objects that can be used with the
awaitkeyword
Advantages:
- Much lighter weight than threads
- Better control over task scheduling
- No GIL limitations for I/O operations
- Cleaner code flow with async/await syntax
Example:
import asyncio
async def fetch_data(url):
# Simulate async I/O operation
await asyncio.sleep(1)
return f"Data from {url}"
async def main():
results = await asyncio.gather(
fetch_data("url1"),
fetch_data("url2"),
fetch_data("url3")
)
return results
asyncio.run(main())
When to use:
- I/O-bound operations (network requests, file operations)
- When you need high concurrency with minimal resource overhead
- Building responsive web services and APIs
Lightweight Processes and Message Passing (Elixir)
Module: Process, send, receive (Core language features)
Elixir’s concurrency model is built on the actor model via lightweight processes that communicate through message passing. Each process is independent with its own memory space and mailbox.
Key concepts:
- Processes: Lightweight actors managed by BEAM VM (10-50KB each)
- Message Passing: Asynchronous, reliable delivery to process mailboxes
- Receive: Pattern matching on incoming messages
- Supervision: Hierarchical process supervision for fault tolerance
- OTP: Open Telecom Platform - battle-tested concurrency framework
Process Lifecycle: SPAWN → RUNNING → RECEIVE/SEND → TERMINATE
Key characteristics:
- Extremely lightweight (BEAM can handle 1M+ processes)
- Isolated memory spaces prevent race conditions
- Pattern matching on messages
- Built-in fault recovery (Let It Crash philosophy)
- Hot code reloading support
Example 1: Basic Process and Message Passing:
defmodule Counter do
def start_link(initial_value) do
spawn_link(fn -> loop(initial_value) end)
end
defp loop(count) do
receive do
{:increment} ->
IO.puts("Count: #{count + 1}")
loop(count + 1)
{:decrement} ->
IO.puts("Count: #{count - 1}")
loop(count - 1)
{:get} ->
IO.puts("Current: #{count}")
loop(count)
:stop ->
IO.puts("Stopping counter")
end
end
end
# Usage
counter_pid = Counter.start_link(0)
send(counter_pid, {:increment}) # Count: 1
send(counter_pid, {:increment}) # Count: 2
send(counter_pid, {:get}) # Current: 2
send(counter_pid, :stop)
Example 2: Worker Pool with Supervisor (Elixir Pattern):
defmodule Worker do
def start_link(worker_id) do
spawn_link(fn -> loop(worker_id) end)
end
defp loop(worker_id) do
receive do
{:job, job_data, caller_pid} ->
result = process_job(job_data)
send(caller_pid, {:result, worker_id, result})
loop(worker_id)
:stop ->
:ok
end
end
defp process_job(data) do
# Simulate work
:timer.sleep(100)
data * 2
end
end
defmodule WorkerSupervisor do
def start_workers(count) do
Enum.map(1..count, fn id ->
{:ok, pid} = Task.start_link(fn -> Worker.start_link(id) end)
pid
end)
end
def distribute_jobs(workers, jobs) do
workers_cycle = Stream.cycle(workers)
Enum.each(Enum.zip(jobs, workers_cycle), fn {job, worker} ->
send(worker, {:job, job, self()})
end)
end
end
# Usage
workers = WorkerSupervisor.start_workers(3)
WorkerSupervisor.distribute_jobs(workers, [1, 2, 3, 4, 5])
# Collect results
results = Enum.map(1..5, fn _ ->
receive do
{:result, worker_id, result} ->
{worker_id, result}
end
end)
Example 3: GenServer (Recommended for Statefull Processes):
defmodule Store do
use GenServer
# Client API
def start_link(initial_state) do
GenServer.start_link(__MODULE__, initial_state, name: :store)
end
def get(key) do
GenServer.call(:store, {:get, key})
end
def put(key, value) do
GenServer.cast(:store, {:put, key, value})
end
# Server Callbacks
@impl true
def init(state) do
{:ok, state}
end
@impl true
def handle_call({:get, key}, _from, state) do
{:reply, Map.get(state, key), state}
end
@impl true
def handle_cast({:put, key, value}, state) do
{:noreply, Map.put(state, key, value)}
end
end
# Usage
{:ok, _} = Store.start_link(%{})
Store.put(:name, "Alice")
Store.put(:age, 30)
Store.get(:name) # "Alice"
Advantages:
- Fault tolerance with supervisor trees
- Hot code reloading for live updates
- Distributed computing built-in
- Excellent for systems requiring high availability and resilience
- Pattern matching makes message handling elegant
When to use:
- Distributed systems requiring fault tolerance
- Real-time applications (chat, notifications, IoT)
- Telecom and messaging platforms
- Systems requiring 99.99%+ uptime
- When you need automatic process supervision and recovery
Goroutines and Channels (GO)
Package: goroutine (part of Go runtime)
GO’s concurrency model is built on goroutines (lightweight threads) and channels (typed conduits for communication between goroutines). This is fundamentally different from traditional thread-based models.
Key concepts:
- Goroutines: Managed by the GO runtime, cheaper than OS threads (thousands to millions can coexist)
- Channels: Enable safe communication and synchronization between goroutines
- Scheduler: The GO runtime scheduler multiplexes many goroutines onto OS threads
- CSP Model: Communicating Sequential Processes - “Share memory by communicating, don’t communicate by sharing memory”
Goroutine Lifecycle: CREATED → RUNNABLE → RUNNING → BLOCKED → TERMINATED
Key characteristics:
- Ultra-lightweight (can spawn 100k+ goroutines easily)
- Automatic scheduling by the runtime
- Memory efficient (each goroutine ~2KB vs threads ~1-2MB)
- First-class language support for concurrency
Thread Synchronization with Channels:
- Unbuffered channels: Block until both sender and receiver are ready
- Buffered channels: Decouple sender and receiver for limited buffering
- Select statement: Multiplex communication on multiple channels
- Mutex: Traditional mutual exclusion locks when needed
Example:
package main
import "fmt"
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// Start workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Send jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Collect results
for a := 1; a <= numJobs; a++ {
fmt.Println(<-results)
}
}
Advantages:
- Exceptional performance for I/O-bound concurrent systems
- Simple, intuitive syntax for parallel programming
- Minimal runtime overhead
- Excellent for building scalable network services, web servers, and microservices
When to use:
- High-performance web servers and APIs
- Microservices and distributed systems
- Real-time applications with many concurrent connections
- When you need predictable performance without GIL limitations
Tasks and Async/Await (C#)
Namespace: System.Threading.Tasks (Core .NET framework)
C# offers modern task-based asynchronous programming with async/await syntax, combined with traditional threading for CPU-bound work. Tasks represent pending or completed asynchronous operations.
Key concepts:
- Tasks: Represent asynchronous operations, allowing composition and chaining
- async/await: Syntactic sugar for writing asynchronous code that reads like synchronous code
- ThreadPool: Managed thread reuse for background operations
- Cancellation: Built-in cancellation token support for graceful shutdown
- Continuations: Chain operations together with
.ContinueWith()
Task Lifecycle: CREATED → SCHEDULED → RUNNING → COMPLETED (or FAULTED, CANCELED)
Key characteristics:
- Modern async/await syntax (similar to Python but more polished)
- Excellent integration with frameworks (ASP.NET, Blazor)
- Strong tooling support in Visual Studio
- Automatic thread management
- Built-in exception handling in async contexts
Example 1: Basic Async/Await Pattern:
using System;
using System.Threading.Tasks;
// Async method returns Task<T>
async Task<string> FetchDataAsync(string url)
{
// Simulate async I/O (network call)
await Task.Delay(1000);
return $"Data from {url}";
}
// Usage
async Task Main()
{
var result = await FetchDataAsync("https://api.example.com");
Console.WriteLine(result); // Data from https://api.example.com
// Concurrent operations
var task1 = FetchDataAsync("url1");
var task2 = FetchDataAsync("url2");
var task3 = FetchDataAsync("url3");
var results = await Task.WhenAll(task1, task2, task3);
foreach (var data in results)
{
Console.WriteLine(data);
}
}
Example 2: Task Parallel Processing (CPU-Bound):
using System;
using System.Threading.Tasks;
// CPU-intensive work distributed across threads
int ComputeHeavy(int number)
{
long result = 0;
for (long i = 0; i < 100_000_000; i++)
result += i * number;
return (int)(result % int.MaxValue);
}
Task Main()
{
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
// Parallel.ForEach distributes work across ThreadPool threads
Parallel.ForEach(numbers, number =>
{
var result = ComputeHeavy(number);
Console.WriteLine($"Number {number}: {result}");
});
// Or with Task.Run for explicit task control
var tasks = numbers.Select(n => Task.Run(() => ComputeHeavy(n))).ToArray();
var results = Task.WaitAll(tasks);
return Task.CompletedTask;
}
Example 3: Producer-Consumer Pattern with Channels:
using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
async Task ProducerAsync(ChannelWriter<int> writer)
{
for (int i = 0; i < 10; i++)
{
await writer.WriteAsync(i);
await Task.Delay(100);
}
writer.TryComplete(); // Signal completion
}
async Task ConsumerAsync(ChannelReader<int> reader)
{
await foreach (var item in reader.ReadAllAsync())
{
Console.WriteLine($"Consumed: {item}");
await Task.Delay(200);
}
}
async Task Main()
{
var channel = Channel.CreateUnbounded<int>();
// Run producer and consumer concurrently
var producer = ProducerAsync(channel.Writer);
var consumer = ConsumerAsync(channel.Reader);
await Task.WhenAll(producer, consumer);
Console.WriteLine("Done!");
}
Example 4: Cancellation with CancellationToken:
using System;
using System.Threading;
using System.Threading.Tasks;
async Task LongRunningOperationAsync(CancellationToken ct)
{
try
{
for (int i = 0; i < 10; i++)
{
ct.ThrowIfCancellationRequested(); // Check for cancellation
Console.WriteLine($"Working... {i}");
await Task.Delay(1000, ct); // Async delay respects cancellation
}
Console.WriteLine("Completed successfully");
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}
}
async Task Main()
{
using var cts = new CancellationTokenSource();
// Cancel after 3.5 seconds
cts.CancelAfter(3500);
await LongRunningOperationAsync(cts.Token);
}
Example 5: Custom Async Iterator (Async Enumerable):
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
// Returns IAsyncEnumerable - can be awaited in foreach
async IAsyncEnumerable<int> FetchDataStreamAsync()
{
for (int i = 1; i <= 5; i++)
{
// Simulate async data fetching
await Task.Delay(500);
yield return i * 10;
}
}
async Task Main()
{
// Await foreach for async iteration
await foreach (var item in FetchDataStreamAsync())
{
Console.WriteLine($"Item: {item}");
}
}
Example 6: Task Synchronization with Lock:
using System;
using System.Threading.Tasks;
class Counter
{
private int _count = 0;
private readonly object _lock = new object();
public void Increment()
{
lock (_lock) // Mutual exclusion
{
_count++;
}
}
public int GetCount()
{
lock (_lock)
{
return _count;
}
}
// Modern alternative: ReaderWriterLockSlim or Semaphore
}
async Task Main()
{
var counter = new Counter();
// Multiple concurrent increments
var tasks = Enumerable.Range(0, 10)
.Select(_ => Task.Run(() => counter.Increment()))
.ToArray();
await Task.WhenAll(tasks);
Console.WriteLine($"Final count: {counter.GetCount()}"); // 10
}
Advantages:
- Modern, clean async/await syntax
- Excellent framework integration (ASP.NET Core, Blazor)
- Strong Visual Studio tooling and debugger support
- Task composition with
WhenAll,WhenAny,ContinueWith - Built-in cancellation token support
- Good performance for both I/O and CPU-bound work
When to use:
- Enterprise applications and business logic
- ASP.NET Core web services and APIs
- Windows desktop applications
- Game development (Unity)
- Systems where .NET ecosystem is already in use
Comparison Summary
| Aspect | Python | Elixir | C# | GO |
|---|---|---|---|---|
| Concurrency Model | Threads (limited by GIL), async/await, multiprocessing | Lightweight processes (BEAM VM) | Threads, async/await, Tasks | Goroutines, Channels |
| Performance | Good for I/O, limited for CPU tasks | Excellent for high concurrency | Good balance | Excellent for I/O & concurrent systems |
| Scalability | Moderate (GIL bottleneck for threads) | Excellent (handle millions of processes) | Good (modern .NET) | Exceptional (millions of goroutines) |
| Learning Curve | Low for imperative programmers | Steeper (functional paradigm) | Moderate (familiar C-like syntax) | Very Low (simple goroutine syntax) |
| Memory per Unit | ~1-2 MB per thread | ~10-100 KB per process | ~500 KB per task | ~2 KB per goroutine |
| Best For | Data processing, web APIs, scripting | Telecom, messaging, real-time systems | Enterprise apps, Windows integration | Web servers, microservices, distributed systems |
| Async Support | asyncio (manual) | OTP (automatic) | Task-based (modern) | Native (first-class) |
| Use Case Sweet Spot | Backend services, ML/AI | Fault-tolerant distributed systems | Enterprise applications | High-concurrency services, APIs |
Performance Benchmarks (4-Language Comparison)
Benchmark 1: Goroutine/Thread Creation Speed
Creating and spawning concurrent units:
| Language | Concurrent Units | Creation Time | Memory/Unit | Total Memory |
|---|---|---|---|---|
| GO | 1,000,000 goroutines | ~1-2 seconds | ~2 KB | ~2 GB |
| Python (threads) | 10,000 threads | ~5-10 seconds | ~1-2 MB | ~10-20 GB |
| C# (tasks) | 100,000 tasks | ~2-3 seconds | ~500 KB | ~50 GB |
| Elixir | 100,000 processes | ~3-5 seconds | ~10-50 KB | ~1-5 GB |
References: GO concurrency patterns, Python threading limits
Benchmark 2: Web Server Concurrent Connections
Handling concurrent HTTP requests (simulated):
| Language | Concurrent Requests | Throughput (req/s) | Memory Usage | Latency (p99) |
|---|---|---|---|---|
| GO (net/http) | 100,000 | ~50,000-100,000 | 200-300 MB | <10ms |
| Python (asyncio) | 10,000 | ~3,000-5,000 | 500 MB | 50-100ms |
| C# (ASP.NET Core) | 50,000 | ~20,000-40,000 | 400-600 MB | 20-50ms |
| Elixir (Phoenix) | 100,000+ | ~30,000-60,000 | 300-500 MB | 10-20ms |
Benchmark sources:
- TechEmpower Benchmarks - Web Frameworks (Real-world HTTP server comparisons)
- Loris Cro: GO vs Elixir for Web Services
- Python asyncio performance
Benchmark 3: CPU-Intensive Tasks (Fibonacci)
Computing fibonacci(40) with 4 concurrent tasks:
Python (multiprocessing): ~4.2 seconds (4 cores) / ~16.8 seconds (1 thread, GIL)
Elixir: ~5.1 seconds (4 cores)
C# (Parallel): ~3.8 seconds (4 cores)
GO: ~3.5 seconds (4 cores)
Key insight: Python threading is 4x slower due to GIL; multiprocessing works but has overhead.
References:
Benchmark 4: Memory Efficiency at Scale
Spawning 100K concurrent units:
| Language | Total Memory | Memory per Unit | Context Switch Time |
|---|---|---|---|
| GO | ~200 MB | ~2 KB | <1μs (user-space) |
| Python (processes) | ~100-200 GB | ~1-2 MB | ~10μs (OS kernel) |
| C# (ThreadPool) | ~20-30 GB | ~500 KB | ~5-10μs (OS kernel) |
| Elixir | ~5-10 GB | ~50-100 KB | ~1-5μs (BEAM scheduled) |
Performance characteristics:
- GO: Unbeatable at scale due to user-space scheduling and minimal overhead
- Elixir: Excellent BEAM VM scheduler, competes with GO for massive concurrency
- C#: Good for 1000s of concurrent operations, ThreadPool limits at 10K+
- Python: Struggles beyond 1000 OS threads; multiprocessing adds IPC overhead
References:
Real-World Recommendations
Use GO when:
- Building high-performance API servers (millions of concurrent connections)
- You need simplicity and fast development with excellent concurrency
- Performance predictability is critical
- Example: HTTP servers, gRPC services, microservices, WebSocket servers
Use Python when:
- Rapid prototyping and developer productivity are priorities
- Integrating with rich data science/ML ecosystem
- GIL limitations are acceptable for I/O-bound workloads
- Example: Web services (FastAPI, asyncio), data pipelines, backend APIs
Use Elixir when:
- Building distributed, fault-tolerant systems requiring high availability
- Need Erlang’s battle-tested OTP framework
- Message passing and actor model fit your problem domain
- Example: Real-time systems, telecom, live updates, messaging platforms
Use C# when:
- Enterprise ecosystem and Windows integration are needed
- Using .NET Core for cross-platform development
- Familiar C-like syntax with enterprise tooling
- Example: Enterprise applications, cloud services (Azure), Windows applications
Seven Concurrency Models
The book “Seven Concurrency Models in Seven Weeks” by Paul Butcher introduces seven fundamental approaches to concurrency:
- Threads and Locks - Traditional shared-memory concurrency
- Functional Programming - Immutability and functional transformations
- Separation of Concerns - Actor model and message passing
- Shared Variable Concurrency - Clojure’s STM (Software Transactional Memory)
- Map-Reduce - Distributed data processing
- Lambda Architecture - Batch and real-time processing
- Data Parallelism - GPU and vector processing
Key Takeaways
- Python: Excellent for I/O-bound tasks via asyncio; avoid threading for CPU tasks due to GIL; consider multiprocessing for CPU-bound work
- Elixir: Superior for systems requiring massive concurrency, fault tolerance, and distributed message passing
- C#: Modern, versatile with excellent async/await support; good balance of performance, scalability, and enterprise ecosystem
- GO: Best-in-class for concurrent I/O-bound systems; exceptional scalability (millions of goroutines); simple, elegant syntax
Golden Rule:
- High-concurrency I/O-bound systems → GO or Elixir
- Enterprise applications with Windows integration → C#
- Machine learning, data science, or existing Python expertise → Python
- Fault-tolerant distributed systems → Elixir
Resources
Books:
- Seven Concurrency Models in Seven Weeks: When Threads Unravel by Paul Butcher
- Mastering Python High Performance by Fernando Doglio
- The GO Programming Language by Alan Donovan & Brian Kernighan
- Programming Elixir by Dave Thomas
- Pro C# by Andrew Troelsen
Official Documentation:
Articles & Benchmarks:
- Wiki: Concurrency
- TechEmpower Web Framework Benchmarks
- Real Python: Threading
- Real Python: Python GIL
- Python asyncio We Did It Wrong
- Waiting in asyncio
- Vectorization Alternatives in Python
- Vectorization in Python
- Elixir Use Cases
- GO vs Rust Performance
- Concurrency in Different Languages
Video Resources: