
Set up your development environment for the course using Visual Studio 2019, GCC, and Compiler Explorer, then configure C++ standards and features like coroutines and parallel STL.
Explore the basics of parallel computing, including von Neumann architecture, processes, threads, context switching, and parallelism concepts. Differentiate concurrency from parallelism and compare task-level versus data-level parallelism across multi-core systems.
In this quiz we are going to explore parallel programming in general
Launch a thread by including thread header and constructing a thread with a callable object (function, lambda, or class with operator), then print thread ids and use join to synchronize.
Launch three threads from the main thread to run function a, function b, and a test function, printing hello messages and determining the finish order including the main thread.
learn joinability governs thread lifecycles, call join or detach to avoid unsafe programs and std terminate. discover how joinable reveals a thread's status and that default-constructed threads are non joinable.
Understand how join and detach create synchronization between threads, where join blocks the calling thread until the launched thread finishes and detach lets the caller continue.
Explore safe thread joining in exception scenarios using a thread guard with RAII. The destructor calls join to guarantee proper cleanup and avoid terminate.
Build a multithreaded console program that simulates ship operations with captain, engine crew, and cleaners; process commands (1 clean, 2 full speed, 3 stop engine) and exit on 100.
Demonstrate risks of passing a reference to a variable across threads, showing how detaching a thread can outlive object lifetimes and trigger access violations; advocate passing by value instead.
Learn to transfer thread ownership using the move constructor and std::move; copying is disallowed, with implicit moves on temporaries and lifecycles via join or detach.
Learn to query and print thread IDs with get_id, explore joining and zero IDs for inactive threads, and use sleep_for, yield, and hardware_concurrency to optimize thread usage.
Improve a ship sailing simulation by implementing engine and cleaning workqueues with dedicated threads that execute queued commands and stop on user input.
Explore std::accumulate in C++, demonstrating both versions: sum with an initial value, product with a binary operation, and a lambda example printing vector elements separated by dashes.
Implement a parallel accumulate by dividing data into blocks, computing per-block sums in threads, and reducing results, using a minimum block size and hardware concurrency to manage overhead.
Explain thread local storage, where each thread has its distinct object that persists during its execution and initializes at thread start, demonstrated by per-thread increments with a global atomic integer.
Debug a C++ application in Visual Studio with breakpoints, stepping, watches, and call stacks to diagnose issues in normal and parallel applications.
Explore locking mechanisms in C++11 and beyond, including mutexes, to protect shared memory during multi-threaded operations such as matrix computations and synchronized access to shared resources.
Explore invariants in doubly linked lists and how broken invariants during updates create race conditions. Learn to prevent them with mutexes.
Protect shared data from race conditions by using mutexes to ensure mutual exclusive access when threads push to a list, then manage locking with lock_guard for scope-based safety.
Explore how mutexes protect shared data and avoid two pitfalls: returning pointers to protected data and passing untrusted code to a protected structure, even with lock guards.
Learn to implement a thread safe stack with a mutex, using a singly linked list for push and pop operations, and examine race conditions in concurrent access.
Implement a thread-safe stack by wrapping std::stack operations with a mutex and lock guard to ensure mutual exclusion for push, pop, top, and empty, and explore race conditions between operations.
Learn to fix race conditions in a thread-safe stack by combining top and pop with an emptiness check. Store values as shared pointers to avoid exceptions and provide safe returns.
Explore how deadlocks arise when threads hold multiple mutexes and wait on each other, and how improper join usage can trigger deadlocks even without mutexes.
Explore unique locks as a general purpose mutex ownership wrapper, with move semantics and optional deferred locking to avoid deadlock, transferring mutex ownership between scopes for X and Y operations.
Explore how condition variables and futures synchronize threads through a bus travel analogy, modeling arrival, distance, and wake-up behavior with threads that drive, stay awake, or nap.
Demonstrate how a condition variable coordinates a driver and a passenger using unique_lock, wait with a lambda condition, and notify_one, handling destination arrival and possible spurious wakeups.
Explore a thread-safe queue implementation using condition variables, detailing the queue interface (push, pop, front, empty, size) and race-condition handling with wait pop for safe concurrency.
Explore how futures enable non-blocking asynchronous tasks by initiating operations, obtaining a future, and using get to retrieve results, with blocking when needed.
Implement a thread-safe queue in C++ using a mutex and a condition variable. Wrap std::queue with lock guards, use unique_lock in wait pop, and apply shared_ptr notify_one to wake threads.
Explore asynchronous operations with std::async, including launch policies (async, deferred, or both) and futures that return results from addition and subtract functions and a print task.
Implement parallel accumulate using asynchronous tasks and futures, dividing input into two parts with a minimum element count of 1000 to avoid oversubscription, and processing with std::async.
Wrap a callable with std::packaged_task to run asynchronously, move it to a thread, and obtain a future, using the return type then argument types in its template parameters.
Learn how std::promise and std::future enable cross-thread value transfer, with set_value signaling readiness and future waiting, illustrated by a main thread and a print thread and a deadlock caution.
Propagate exceptions between threads using std::promise and futures, calling set_exception when input is negative, while one thread waits on the future as another computes the square root.
Design lock based thread safe data structures and algorithms to maximize concurrency with a single mutex, preserving invariants and avoiding race conditions and deadlocks.
Implement a thread-safe queue with a singly linked list using head and tail pointers, guarding push and pop with locks and a dummy node to prevent race conditions.
Implement a thread safe queue by locking the tail in push and both head and tail in pop, using scope based locking and a get_tail method to maximize concurrency.
Explore parallel STL in C++17, comparing parallel and sequential algorithms, and learn to apply execution policies (par, seq, par_unseq) to operations like sort, with performance considerations and implementation details.
Explore parallel quicksort in modern c++ by implementing a sequential version first, then parallelizing a recursive call with std::async and future, using pivot, partition, and combining parts.
Demonstrates parallel for each using package task and async task, dividing work, benchmarking against STL sequential and parallel implementations, and measuring execution times with multiple approaches.
Implement a parallel find algorithm by dividing input into blocks, using a global atomic flag and a promise or async tasks to stop other threads once a match is found.
Implement a parallel find using std::async and recursive division of the data block, propagating exceptions with a done flag and using futures to synchronize.
Learn the prefix sum algorithm and its parallel variants using partial sum, inclusive scan, and exclusive scan, and compare sequential versus parallel performance with execution policies.
Explore how to implement parallel matrix operations in C++ by leveraging data independence. Learn about matrix multiplication and transpose stored in a row-major, single-dimensional array and how to parallelize them.
Implement a parallel matrix multiply by dividing the output data into start-end chunks processed by multiple threads, then compare performance against the sequential version.
Learn to implement a parallel matrix transpose in C++ using a sequential baseline, index flipping, and workload division across threads, with performance tradeoffs as matrix size grows.
Discover jthread, a C++20 thread that manages its life cycle and supports interruption via stop tokens, avoiding explicit join or detach.
Implement our own interruptible jthread by wrapping std::thread, enabling auto joining in the destructor, and using an interrupt flag with an interrupt point to signal and check interruptions.
Explore the basics of C++ coroutines in C++20, including await, suspend, and lazy generators; learn how coroutine objects, heap and stack state, and resume mechanics enable non-blocking, on-demand computation.
Explore c++ coroutines by defining a promise type, a coroutine handle, and a coroutine state object, then control suspension and resume execution with ko await and suspend always.
Explore lazy generators with C++ coroutines and co_yield to generate on-demand sequences via a generator and promise type.
Barrier synchronizes threads by holding them until a count is reached, then releases them to proceed. The lecture demonstrates a boost barrier example and two approaches: spin-wait and condition variables.
Learn how the C++20 standard stop source and stop token enable thread cancellation via a shared state, with getToken producing cheap, composable tokens.
Explore how C++20 stop_callback registers a callback with a stop token to wake blocked threads via a synchronously fired lambda, using RAII and immediate firing for stopped tokens.
Demonstrate how a binary semaphore uses a 0/1 counter for inter-thread signaling, not ownership, with a processor waiting for data and a main thread release, plus a ping-pong pattern.
Explore C++23 standard generators that integrate with ranges, enabling co-yield based fibonacci, parameterized generators, prime number streams, and flattening nested vectors with range adapters and fold left.
demonstrates a cancellable multistage pipeline in c++20 using bounded channels, semaphores, and a stop token to safely propagate end signals and apply back pressure from producer to filter to consumer.
Explore how atomic types and memory ordering semantics in the C++ memory model enable lock-free data structures and algorithms, using indivisible read-modify-write operations such as post-increment on integers.
Examine std::atomic_flag, the simplest atomic type representing a boolean value, and initialize atomic_flag variables with the atomic flag in it, then use test_and_set and clear to observe previous values.
Explore std::atomic_bool and its distinction from atomic_flag through not copy constructible semantics and construction from non-atomic booleans. Learn core operations including store, load, exchange and lock free status, plus compare_exchange_weak and compare_exchange_strong.
Explore atomic pointers in C++ concurrency, where the pointer is atomic, not the pointed object, and apply is_lock_free, load, store, compare_exchange_weak, fetch_add, and fetch_sub.
Explore atomic types in modern C++, including flag, bool, int, and pointers, with load, store, exchange, and compare-exchange operations. Require trivial copy assignment and bitwise equality comparability for user-defined types.
Explore how happen-before and inter-thread happen-before relationships enable synchronization between writer and reader threads. See how a data ready flag and an atomic boolean guarantee that pushes occur before reads.
Explore memory ordering options in the C++ memory model, including sequentially consistent, relaxed, acquire, release, acquire-release, and consume, and how to apply them to atomic operations for lock-free data structures.
Examine how modern processors enable instruction reordering through caches and store buffers. Discover how compilers and memory ordering interact with mutexes to ensure thread safety.
Explore memory order relaxed and its lack of inter-thread guarantees, showing how writer and reader can see updates out of order and z may stay zero, unlike sequential consistency.
Discuss memory_order_acquire and memory_order_release roles in synchronization by pairing release stores with acquire loads to establish inter-thread happens-before relationships.
Understand how memory_order_acquire and memory_order_release govern synchronization and visibility of shared state, and why release-acquire pairs require preceding statements to be visible across threads.
Explore the transitive property of synchronization using three threads. Thread one populates an array, thread two uses release and acquire, and thread three observes the data.
Explain the release sequence on an atomic variable, showing how a writer's store with release and readers' loads with acquire establish synchronization points through read modify write operations.
Implement a Spinlock mutex using an atomic flag with test_and_set (memory order acquire) and clear (memory order release) to provide mutual exclusion, demonstrated with a lock guard and two threads.
Explore futex-style waiting on std::atomic to wait for state changes without spinning, using notify_one and notify_all, with demos for atomic ready and a phase state machine.
This lecture introduces atomic ref, wrapping a reference to a non-atomic object to enable atomic access without changing structure layout, using load, store, and fetchAdd with relaxed ordering.
Learn how std::atomic of a shared pointer enables lock-free updates with load, store, exchange, and compare exchange on a pointer. Readers take snapshots while an updater replaces configuration objects atomically.
Build a lock-free, multi-producer, single-consumer queue using modern C++20 features, including atomic wait and notify one, atomic ref, and the Michael Scott QNQ algorithm.
Explore lock-free data structures and thread pool design in modern C++ concurrency, building on mutexes, futures, and memory ordering. Define blocking versus non-blocking paradigms, and distinguish wait-free from lock-free progress.
Explore a lock-free stack implemented with a simple singly linked list, detailing push and pop operations, LIFO behavior, race conditions, and atomic compare-exchange to update the head in multithreaded scenarios.
Demonstrates a lock-free, thread-safe stack pop operation using atomic head manipulation and compare_exchange to avoid race conditions. Addresses memory safety with shared_ptr usage, null checks, and memory leak prevention.
Implement a thread-counted memory reclamation for a stack in concurrent pops, using atomic counters and a to_be_deleted_list, with a try_reclaim phase to safely delete nodes when only one thread remains.
Explore hazard pointers to reclaim memory in a lock-free stack, detailing hazard pointer management, to-be-deleted lists, and safe node reclamation in a concurrent setting.
Implement a pop function for a lock-free stack using a two-count reference counting scheme with external and internal counts, node wrappers, and atomic operations to reclaim memory.
Develop a simple fixed thread pool that submits tasks to a work queue, executes them with worker threads, and supports future-like synchronization for task completion.
Enhances a thread pool by making submit return a future for a packaged task, enabling the calling thread to wait for task completion via futures with a move-only function wrapper.
Minimize contention on the thread pool by using a per-thread local queue alongside a global queue, routing tasks to local queues first and falling back to the global queue.
Implement a work stealing thread pool by adding a work stealing queue and a vector of local queues, enabling pool threads to push, pop, or steal tasks safely.
Install the Cuda toolkit on your Nvidia gpu by checking compute capability and microarchitecture, verify with nvcc, and set up a Visual Studio Cuda project to run your first kernel.
Launch a cuda program by initializing data on the host, transferring to the device, and printing from hello cuda world kernel. Master grid and block layouts with dim3 for 1d/2d/3d.
Discover how the cuda runtime initializes per-thread variables based on a thread's location in the grid and thread block, setting thread id x, y, and z accordingly.
Learn how the CUDA runtime initializes block id and grid dim variables and how block dim and grid dim define thread block layout in x, y, and z.
Compute a unique global index for each thread in a one-dimensional CUDA grid by adding an offset equal to block id x multiplied by blockdim.x, enabling access to array elements.
Learn how to compute unique global indices for a two-dimensional grid by adding row offsets and block offsets to the thread id, ensuring all 16 elements are accessed.
Explore global index calculation for a two-dimensional grid with two-dimensional thread blocks, using tid and offsets to ensure consecutive memory access and unique elements.
Measure CPU and GPU execution times in CUDA programs by capturing clock cycles, translating to seconds; compare transfers, kernel time, and tune block size via trial and error.
learn to implement parallel summation of two arrays using a one-dimensional CUDA grid, with a validity check for the kernel and verification of GPU results against CPU.
Handle runtime errors in CUDA by checking each CUDA API call, using cudaGetErrorString for messages, and applying a file-and-line macro for centralized error reporting.
Explore how to query and print CUDA device properties across compute capabilities, including memory, threads per block, grid size, shared memory, and warp size, using cudaGetDeviceCount and cudaGetDeviceProperties.
C++ programming language can be categorized under many topics. Some say its a general purpose programming language, some say its a object oriented version of C. But I liked to categorized it under system programming language. One characteristic of any system programming language including C++ is that language should be able to execute faster compare to other languages like java etc.
C++ paradigm took sharp turn with the introduction of C++11 standards. The most notable difference with previous version is the introduction of new memory model. Memory model is the key part of any language, and the performance of all the functionalities depends on that language memory model. With new c++ memory model, we can exploit tremendous power of modern multi core processors.
Programming a proper C++ code with better memory reclaim mechanism is tough task. But if we want to code thread safe code which can harvest underline processors true power is much more difficult task. In this course we will have in depth discussion on C++ concurrency features including memory model. We will implements thread safe data structures and algorithms, both lock based manner and lock free manner. Proper lock free implementations of data structures and algorithms will provide unprecedented performance output. Let me listed down key aspects we cover in this course below.
1.Basics of C++ concurrency(threads, mutex, package_task, future ,async, promise)
2.Lock based thread safe implementation of data structures and algorithms.
3.C++ memory model.
4.Lock free implementation of data structures and algorithms.
5.C++20 concurrency features.
5. Proper memory reclaim mechanism for lock free data structures.
6. Design aspects of concurrent code.
7. In depth discussion on thread pools.
8. Bonus section on CUDA programming with C and C++.