Threads
Each a thread ones a program (is a single sequence stream within a process) and has it own register bank. But wait what about systems that has 1 core it have 1 set of register!
It the magic of Thread Scheduler (or just called the scheduler) it switch between threads saving the current register bank and switch the next thread register bank
- Each thread has it own
stackand local variables- the memory is shared between all the threads (aka same address space)

Context Switching


Save execution is the saving of all register values in the stack of the current thread.

Restore does the what is says, it restores the register values from the stack of the thread you are restoring.
💡 Each thread typically has its own stack. This is necessary because threads execute independently and may have their own local variables, function calls, and return addresses. Sharing the same stack between threads would lead to corruption and unpredictable behavior as threads overwrite each other's data. In a multithreaded system, the operating system or runtime allocates separate stack spaces for each thread.
The stack is always located in RAM, not in flash memory. Each thread has its own separate stack region in RAM. The operating system or RTOS allocates a unique stack for each thread, and the stack pointer (SP) register is switched when changing threads.
Classification of Threads

sporadic: like execptions
Thread Control Block (TCB)
Data Structure containing information private to each thread
- Must have:
- Pointer to stack
- Pointer to the next thread
- Additionally:
- Variable to hold thread status
- Variable to hold thread ID
- Variable to hold thread Period
- Variable to hold thread Burst time
- Variable to hold thread Priority
- Etc.
Structure Definition
struct tcb {
int32_t *stackPt; // Pointer to the stack
struct tcb *nextPt; // Pointer to the next thread
uint32_t status; // Thread status
uint32_t Period; // Thread period
uint32_t burstTime; // Thread burst time
};
Example
Task Flow

Example task scheduling for four tasks in a real-time system:
- Task 1:
- Performs an action directly (e.g., controlling hardware or executing logic).
- Task 2:
- Checks the status of a LIDAR sensor.
- If the sensor is busy, the task waits.
- If the sensor is ready, the task proceeds to get data.
- Task 3:
- Similar to Task 2, but for a RADAR sensor.
- Checks the RADAR status and either waits (if busy) or gets data (if ready).
- Task 4:
- Similar to Task 2 and Task 3, but for a Camera sensor.
- Checks the Camera status and either waits (if busy) or gets data (if ready).

Thread Control Block (TCB) Structure:
struct tcb: Defines the basic structure for managing threads.stackPt: Pointer to the thread's stack.nextPt: Pointer to the next thread in the linked list.
- Constants:
NUM_OF_THREADS: Defines the number of threads (4 in this case).STACKSIZE: Defines the size of the stack for each thread (100 bytes).
- Typedef and Variables:
typedef struct tcb tcbType: Creates an aliastcbTypefor thestruct tcb.tcbs[NUM_OF_THREADS]: Array of TCBs for managing multiple threads.currentPt: Pointer to the currently running thread.TCBS_Stack[NUM_OF_THREADS][STACKSIZE]: Allocates stack memory for all threads.
Thread States

only one thread is running at a time
on freertos where is a suspended state

Process vs Thread
A process is an independent program in execution, with its own memory space, resources, and system-level isolation. Processes are heavyweight and require context switching by the operating system. A thread, on the other hand, is a lightweight unit of execution within a process that shares the same memory space and resources as other threads in the same process. Threads are faster to create and switch between, making them ideal for tasks requiring parallelism within a single application.

1. Thread Synchronization
Mutexes (Mutual Exclusion)
Prevent race conditions when multiple threads access shared resources.
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
// Critical section
pthread_mutex_unlock(&lock);
Semaphores
Control access to resources with limited capacity.
sem_t semaphore;
sem_init(&semaphore, 0, 3); // 3 resources available
sem_wait(&semaphore); // Acquire
// Use resource
sem_post(&semaphore); // Release
2. Thread-Local Storage (TLS)
Each thread has its own copy of the variable.
__thread int thread_local_var;
// or
pthread_key_t key;
pthread_key_create(&key, NULL);
pthread_setspecific(key, value);
3. Thread Pools
Reuse threads to avoid overhead of constant creation/destruction.
// Create pool of N threads
// Submit tasks to queue
// Workers pick tasks and execute
// Efficient for handling many short tasks
4. Read-Write Locks
Multiple readers OR one writer at a time.
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
// Multiple readers
pthread_rwlock_rdlock(&rwlock);
// Read data
pthread_rwlock_unlock(&rwlock);
// Single writer
pthread_rwlock_wrlock(&rwlock);
// Write data
pthread_rwlock_unlock(&rwlock);
5. Thread Affinity
Bind threads to specific CPU cores for performance.
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset); // Bind to core 0
pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
6. Thread Cancellation
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
// Cancel thread
pthread_cancel(thread_id);
// Cancellation points: pthread_join, sleep, etc.
7. Barriers
Synchronize multiple threads at a point.
pthread_barrier_t barrier;
pthread_barrier_init(&barrier, NULL, 4); // 4 threads
// Each thread waits here
pthread_barrier_wait(&barrier);
// All proceed together after all arrive
8. Spinlocks
Busy-wait locks for very short critical sections.
pthread_spinlock_t spinlock;
pthread_spin_init(&spinlock, 0);
pthread_spin_lock(&spinlock);
// Very short critical section
pthread_spin_unlock(&spinlock);
9. Memory Models & Atomics
#include <stdatomic.h>
atomic_int counter = 0;
atomic_fetch_add(&counter, 1); // Thread-safe increment
// Memory barriers
atomic_thread_fence(memory_order_seq_cst);
10. Common Problems
Race Conditions
Multiple threads accessing shared data simultaneously.
Deadlock
Threads waiting for each other's locks indefinitely.
Thread A: locks mutex1 → waits for mutex2
Thread B: locks mutex2 → waits for mutex1
Priority Inversion
Low-priority thread holds lock needed by high-priority thread.
Starvation
Thread never gets CPU time due to scheduling.
11. Performance Considerations
- Context switching overhead
- Cache coherency (false sharing)
- Lock contention
- Number of threads (too many = overhead)
- Amdahl's Law - parallelization limits
12. Debugging Tools
- Valgrind (helgrind) - Race condition detection
- ThreadSanitizer - Data race detector
- strace - System call tracing