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

image 6.png

Context Switching

image.png

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

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.

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

image.png

sporadic: like execptions


Thread Control Block (TCB)

Data Structure containing information private to each thread

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

image.png

Example task scheduling for four tasks in a real-time system:

  1. Task 1:
    • Performs an action directly (e.g., controlling hardware or executing logic).
  2. 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.
  3. 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).
  4. 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).

image.png

Thread Control Block (TCB) Structure:

  1. 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).
  2. Typedef and Variables:
    • typedef struct tcb tcbType: Creates an alias tcbType for the struct 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

only one thread is running at a time

on freertos where is a suspended state

image.png

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.

image.png

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

12. Debugging Tools