Skip to main content

Multithreading & Scheduling

A complete technical learning guide for understanding threads, concurrency, synchronization, task scheduling, and high-performance application design.

Overview

Modern applications must handle multiple users, concurrent requests, background jobs, and real-time processing.

Multithreading and scheduling help applications:

  • Improve responsiveness
  • Utilize CPU efficiently
  • Execute tasks concurrently
  • Scale under heavy load

Core Concepts

ConceptDescription
ProcessIndependent running application
ThreadLightweight execution unit inside a process
ConcurrencyMultiple tasks making progress together
ParallelismTasks executing simultaneously
Context SwitchingCPU switching between threads
SynchronizationCoordinating shared resources
DeadlockThreads waiting forever
Race ConditionUnpredictable result from concurrent access
SchedulerDetermines execution order

Process vs Thread

ProcessThread
MemoryOwn memory spaceShares process memory
WeightHeavyweightLightweight
IsolationIsolated from othersRequires synchronization

Concurrency vs Parallelism

Concurrency — Multiple tasks progress over time (context switching on one CPU).

Parallelism — Tasks execute simultaneously on multiple cores.


Thread Lifecycle


Thread Creation

using System;
using System.Threading;

class Program
{
static void PrintNumbers()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
}
}

static void Main()
{
Thread thread = new Thread(PrintNumbers);
thread.Start();
Console.WriteLine("Main Thread");
}
}

Race Condition

int counter = 0;

Parallel.For(0, 1000, i =>
{
counter++; // Not atomic — corrupted result
});

Locking

private static readonly object _lock = new();

lock(_lock)
{
counter++;
}

Deadlock

Preventing Deadlocks

StrategyDescription
Lock OrderingAlways acquire locks in same order
TimeoutAvoid infinite waits
Reduce Lock ScopeKeep locks short
Avoid Nested LocksSimplify synchronization

Thread Pool

Creating threads is expensive. Thread Pool reuses threads.


Task Parallel Library (TPL)

Modern .NET uses Task — prefer this over new Thread().

Task.Run(() =>
{
Console.WriteLine("Running task");
});

Async vs Multithreading

Async/AwaitMultithreading
TypeNon-blockingParallel execution
Best forI/O-boundCPU-bound
WeightLightweightHeavier
UsageUses schedulerUses threads directly

Async/Await Example

public async Task<string> DownloadAsync()
{
using HttpClient client = new();
return await client.GetStringAsync("https://example.com");
}

CPU-Bound vs I/O-Bound

TypeExampleSolution
CPU-BoundImage processingMultithreading
I/O-BoundAPI calls, file readsAsync/Await

Parallel Programming

// Parallel.For
Parallel.For(0, 10, i =>
{
Console.WriteLine(i);
});

// Parallel LINQ (PLINQ)
var result = data
.AsParallel()
.Where(x => x > 10)
.ToList();

Scheduling Algorithms

AlgorithmDescription
First Come First ServeFIFO execution
Round RobinTime slicing — fair, responsive
Priority SchedulingHigher priority first
Shortest Job FirstShort tasks first

Context Switching

CPU switches between threads by saving and loading state. Too many threads → poor performance due to context switching overhead.


Synchronization Primitives

Semaphore (limited access)

SemaphoreSlim semaphore = new(3);

await semaphore.WaitAsync();
try
{
// Access resource
}
finally
{
semaphore.Release();
}

Mutex (exclusive access)

Mutex mutex = new();

mutex.WaitOne();
try
{
// Critical section
}
finally
{
mutex.ReleaseMutex();
}

Cancellation Tokens

CancellationTokenSource cts = new();

Task.Run(() =>
{
while (!cts.Token.IsCancellationRequested)
{
Console.WriteLine("Working");
}
});

cts.Cancel();

Producer Consumer Pattern

BlockingCollection<int> queue = new();

Task.Run(() => queue.Add(1));
Task.Run(() =>
{
int item = queue.Take();
});

Common Multithreading Patterns

PatternUse Case
Producer ConsumerQueues
Thread PoolReusable workers
Fork JoinDivide & merge tasks
Actor ModelIsolated concurrency
PipelineSequential stages

Performance Best Practices

// Prefer Task.Run() over new Thread()
Task.Run(() => Work());

// Minimize lock scope
HeavyWork();
lock(_lock)
{
UpdateSharedData(); // Keep lock short
}

// Atomic operations instead of locks
Interlocked.Increment(ref counter);

Common Pitfalls

PitfallProblem
Too Many ThreadsContext switching overhead
DeadlocksInfinite waiting
Race ConditionsCorrupted data
Blocking CallsReduced scalability
Shared StateComplexity

Advanced Topics

Lock-Free Programming

Uses atomic operations instead of locks.

Interlocked.Increment(ref counter);

Actor Model

Actors communicate via messages — no shared state.

Technologies: Akka.NET, Orleans.

Reactive Programming

Event-driven asynchronous programming with Rx.NET.


Real-World Architecture Example


Cheat Sheet

ConceptKey Idea
ThreadExecution unit
ProcessApplication container
LockSynchronization
SemaphoreLimited access
MutexExclusive access
AsyncNon-blocking I/O
ParallelismMulti-core execution
SchedulerTask management

Interview Questions

Beginner

  1. Difference between process and thread?
  2. What is race condition?
  3. What is deadlock?
  4. What is synchronization?

Intermediate

  1. Difference between async and multithreading?
  2. Explain thread pool.
  3. What is semaphore?
  4. What causes context switching?

Advanced

  1. Explain work stealing.
  2. How does the task scheduler work?
  3. What are lock-free structures?
  4. How would you design a high-throughput scheduler?

Learning Roadmap