Concurrency
Concurrency occurs when multiple copies of a program run simultaneously while communicating with each other. A process can be executed by multiple different threads.
Issues with concurrency
If you write multithreading programs like a crackhead, you can run into problems such as:
Data Race
- When two, or more threads in a single process access the same memory location concurrently
- at least one of the accesses is for writing
- the threads are not using any exclusive locks to control their accesses to that memory
When three of these conditions hold, the order of accesses is non-deterministic
Race Condition
A race condition occurs when two or more threads can access shared data and they try to change it at the same time. Because the thread scheduling algorithm can swap threads at any time, you don’t know the order in which the threads will attempt to access the shared data.
Concurrency vs Parallelism
Parallelism - multiple copies of the same program run simultaneously, but they are executed on different data
Concurrency - involves a shared memory location, and the different threads actually read the information provided by the previous threads
Terminologies
Thread Pool
- collection of worker threads that efficiently execute asynchronous callbacks on behalf of the application
- It is primarily used to reduce the number of application threads and provide management of worker threads
- Application can queue work items, associate work with waitable handles, automatically queue based on a timer, and bind with I/O
Which applications can benefit from thread pool?
- highly parallel applications that dispatch a large number of small work items asynchronously
- applications that create a large number of threads that each run for a short time
- applicaitons that require parallel processing (web browsers i.e. opening multiple tabs)
Semaphores
- It is a non-negative variable and shared between threads
- It is used in thread synchronization and solving the critical section problem
- Semaphores are of 2 types:
- Binary Semaphore - aka Mutex lock, it can have only 2 values (0 or 1)
- Counting Semaphore - its value can range over an unrestricted domain, it is used to control access to a resource that has multiple instances
C++ Syntax - Parallelism and Concurrency
std::thread
std::thread
is used to create an execution thread instance
#include<iostream>
#include<thread>
int main() {
std::thread t([](){
std::cout << "hello world" << std::endl;
})
// join a thread i.e. blocks the calling thread until the thread whose join method is called has completed
t.join();
return 0;
}
Mutex and Critical Section
Create a mutex by instantiating std::mutex
Member functions - lock()
and unlock()
. Instead of using lock and unlock for every mutex, you can use a lock_guard()
which bounds the lifetime of the mutex lock to the lifetime of the object (RAII)
Moreover, unique_lock
is more flexible than lock_guard
unique_lock
manages the locking and unlocking on a mutex object with exclusive ownership (no other unique_lock
objects owning the ownership of a mutex). So, it is recommended to use unique_lock
in concurrent programming. Below example uses unique_lock
Code: Mutex Basics