
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 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.
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.
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.
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 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.
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.
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.
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.
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 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 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.
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.
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.
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.
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.
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.
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.
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++.