std::thread is the standard C++11 way to spawn a new OS-level thread. Each std::thread object represents a single, joinable thread of execution. Under the hood, on Linux/Android, it wraps pthread_create() and uses native threads
Creating a simple thread
#include <iostream>
#include <thread>
void say_hello() {
std::cout << "Hello from thread!\n";
}
int main() {
std::thread t(say_hello); // Spawn new thread
t.join(); // Wait for thread to finish
std::cout << "Back in main()\n";
return 0;
}
Important points to note
.join() -> Waits for the thread to complete (like pthread_join).detach() -> Lets thread run independently (becomes un-joinable)
RAII danger -> If std::thread is not joined or detached before destruction → crash
Thread as a class Member
Creating a local variable looks easy, but how to init a thread if thread is member of a class, you need to mention exact scoped function and this pointer while creating the thread, ie.
worker_thread = std::thread(&Worker::run, this);
#include <iostream>
#include <thread>
#include <atomic>
class Worker {
public:
Worker() : running(false) {}
void start() {
running = true;
worker_thread = std::thread(&Worker::run, this);
}
void stop() {
running = false;
if (worker_thread.joinable()) {
worker_thread.join();
}
}
~Worker() {
stop();
}
private:
void run() {
while (running) {
std::cout << "Running in worker thread\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
std::atomic<bool> running;
std::thread worker_thread;
};
Setting thread’s name
pthread_setname_np(pthread_self(), name); // Max 15 chars , This API can be used to set thread name.
Setting Thread priority
In Scheduling article, we talked about different schedulings like RT, then different priorities. For that we can use pthread_setschedparam to evelvate the priority. RT priorities need CAP_SYS_NICE permission, you can define it in a rc file in Android.
void elevate_priority(int prio = 80) {
sched_param sch_params;
sch_params.sched_priority = prio;
if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sch_params) != 0) {
perror("Failed to set thread priority");
} else {
std::cout << "Priority set to " << prio << "\n";
}
}
Critical Section and data race
When multiple threads try to access same global variable, it can cause data race, we can use mutex to prevent it.
A std::mutex ensures that only one thread at a time can enter a critical section — any other thread trying to acquire the lock will block until it’s released. It maps directly to a binary semaphore or lock in OS kernels
Try running below code , from multiple threads, you may get a incorrect ans.
int counter = 0;
void unsafe_increment() {
for (int i = 0; i < 10000; ++i) {
++counter;
}
}
// by the end should be 10000 + 10000, if 2 threads are calling this function, but it may not be the ans, because of data race.
We can simply use a mutex to prevent data racing.
std::mutex counter_mutex;
void safe_increment() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(counter_mutex);
++counter;
}
}
There are multiple lock_guard, this table represents their uses, as per task these can be used.
| Goal | Use This | Why |
|---|---|---|
| Just protect critical section | std::lock_guard | Small scope, RAII, low overhead |
Use with condition_variable | std::unique_lock | Required for .wait(lock) |
| Need to unlock early | std::unique_lock | Allows lock.unlock() mid-scope |
| Lock multiple mutexes safely | std::scoped_lock(m1, m2) | Avoids deadlocks via atomic lock ordering |
| Allow multiple readers | std::shared_mutex | One writer or many readers at once |
| Want full manual control | std::mutex | More error-prone, but flexible |
std::mutex m;
m.lock();
// critical section
m.unlock();
std::lock_guard<std::mutex> lock(m);
// auto-unlocked when lock goes out of scope
std::unique_lock<std::mutex> lock(m);
cond_var.wait(lock); // required with cond vars
lock.unlock(); // manually unlock if needed
std::mutex a, b;
std::scoped_lock lock(a, b); // Avoid deadlock when locking multiple mutexes
std::shared_mutex sm;
void reader() {
std::shared_lock lock(sm); // Many readers can hold this
}
void writer() {
std::unique_lock lock(sm); // Only one writer allowed
}
Communication b/w threads
A condition_variable allows one thread to wait until it’s told by another thread that a condition has changed. It’s the thread-safe alternative to:
polling loops (CPU waste),
busy-waiting (while(!ready) {}),
Interesting thing note here is the wait condition
cv.wait(lock, [] { return ready; }); it is called the predicate, it is read as wait until predicate is true, ie if the predicate is false then thread will wait other it won’t wait.
#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // Wait until `ready == true`
std::cout << "Worker thread proceeding...\n";
}
void signaler() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one(); // Wake one thread
}
int main() {
std::thread t1(worker);
std::thread t2(signaler);
t1.join();
t2.join();
}
cv.wait(lock):
- Unlocks the mutex.
- Blocks the thread on a kernel futex wait.
- Re-locks the mutex after being signaled.
cv.notify_one() or notify_all():
- Signals a waiting thread to re-acquire the mutex and proceed.
| Function | Purpose |
|---|---|
cv.wait(lock, predicate) | Wait until signaled and predicate is true |
cv.notify_one() | Wake one waiting thread |
cv.notify_all() | Wake all waiting threads |
wait_for() | Timed wait |
wait_until() | Deadline-based wait |
Leave a comment