Skip to main content

What is Concurrent Programming? (With Code Examples)

· 4 min read
PSVNL Sai Kumar
Senior Software Development Engineer, Oracle

Concurrent programming refers to a paradigm where multiple tasks make progress independently, potentially interleaved in execution. This is not the same as parallel computing (tasks running at literally the same instant on multiple cores) — concurrency is about structure, not necessarily simultaneous physical execution.

Key Features of Concurrent Programming

  1. Task Independence: Multiple tasks can start, execute, and complete in overlapping time periods.
  2. Resource Sharing: Tasks share resources like memory or CPU time, but they are designed to avoid conflicts through techniques like synchronization.
  3. Improved System Utilization: Concurrent programs make better use of system resources by avoiding idle waiting, especially in I/O-bound applications.

Real-World Example of Concurrent Programming

Let’s consider a real-world scenario where a web server handles multiple user requests. Using concurrent programming, the server doesn't need to wait for one request to finish before starting the next one. Instead, it processes them concurrently, improving overall responsiveness.

Python Example: Downloading Multiple Web Pages Concurrently

import time
import concurrent.futures
import requests

# A function to download a web page
def download_page(url):
print(f"Starting download: {url}")
response = requests.get(url)
time.sleep(1) # Simulating some processing time
print(f"Finished downloading {url}")
return response.content

urls = [
'https://www.example.com',
'https://www.example.org',
'https://www.example.net'
]

# Using concurrent programming to download all pages at once
start_time = time.time()

with concurrent.futures.ThreadPoolExecutor() as executor:
results = executor.map(download_page, urls)

end_time = time.time()
print(f"Downloaded all pages in {end_time - start_time} seconds")

Importance of Concurrent Programming

Concurrent programming is essential in systems where responsiveness and resource efficiency are critical, such as:

  • Web servers handling multiple requests.
  • Mobile apps performing background tasks.
  • Operating systems managing multiple processes.

Concurrency vs. Parallelism

This distinction matters:

  • Concurrency: Multiple tasks are in progress at the same time. They may or may not run simultaneously. On a single CPU, they take turns.
  • Parallelism: Multiple tasks run at the exact same instant on multiple CPU cores.

You can have concurrency without parallelism (single-core multitasking), and you can have parallelism without well-designed concurrency (multiple threads that don't coordinate properly).

Thread-Based Concurrency

The traditional model uses operating system threads. Each task gets a dedicated thread; the OS scheduler decides when each thread runs.

Java example — two threads running concurrently:

Thread t1 = new Thread(() -> System.out.println("Task 1"));
Thread t2 = new Thread(() -> System.out.println("Task 2"));
t1.start();
t2.start();

Problems with thread-per-request:

  • Threads are expensive (each needs a stack, typically 1–8 MB)
  • Context switching overhead at high counts
  • Shared state needs explicit synchronization (locks, semaphores)

Race Conditions and Synchronization

A race condition occurs when two threads access shared state without coordination and the result depends on timing.

# Unsafe — both threads might read x=0, both add 1, both write x=1
x = 0

def increment():
global x
x = x + 1 # read-modify-write is not atomic

# With a lock — safe
import threading
lock = threading.Lock()

def safe_increment():
global x
with lock:
x = x + 1

Go's Concurrency Model: Goroutines and Channels

Go's approach is famously efficient. Goroutines are lightweight (2 KB stack initially, grows as needed) and scheduled by the Go runtime, not the OS.

package main

import (
"fmt"
"sync"
)

func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// do work...
fmt.Printf("Worker %d done\n", id)
}

func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
}

Go's mantra: "Do not communicate by sharing memory; instead, share memory by communicating." — use channels to pass data between goroutines.

Concurrent vs. Sequential Performance

The speedup from concurrency depends on the task type:

Task typeBenefit from concurrency
I/O-bound (network, disk)High — threads can wait without blocking others
CPU-bound (calculation)Limited by core count (Amdahl's Law)
MixedSignificant — I/O and CPU work can overlap