Skip to content

🧵 std::thread — Spawning & Joining Threads

std::thread là building block cơ bản để tạo concurrent execution trong C++.

Tạo Thread đầu tiên

Hello World — Multithreaded Edition

cpp
#include <iostream>
#include <thread>

void sayHello() {
    std::cout << "Hello from thread!\n";
}

int main() {
    // Tạo thread mới, chạy hàm sayHello()
    std::thread t(sayHello);
    
    // Chờ thread hoàn thành
    t.join();
    
    std::cout << "Main thread done.\n";
    return 0;
}

Output (có thể thay đổi thứ tự):

Hello from thread!
Main thread done.

Thread Lifecycle

┌─────────────────────────────────────────────────────────────────┐
│                    THREAD LIFECYCLE                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   std::thread t(func);                                          │
│         │                                                       │
│         ▼                                                       │
│   ┌─────────────┐                                               │
│   │   CREATED   │ ─── Thread bắt đầu chạy ngay lập tức          │
│   └─────────────┘                                               │
│         │                                                       │
│         ├─────────────────┬─────────────────┐                   │
│         ▼                 ▼                 ▼                   │
│   ┌──────────┐      ┌──────────┐      ┌──────────┐              │
│   │  join()  │      │ detach() │      │ (nothing)│              │
│   │          │      │          │      │          │              │
│   │  Block   │      │  Fire &  │      │  ❌ UB!  │              │
│   │  & Wait  │      │  Forget  │      │  Crash!  │              │
│   └──────────┘      └──────────┘      └──────────┘              │
│         │                 │                 │                   │
│         ▼                 ▼                 ▼                   │
│   ┌──────────┐      ┌──────────┐      ┌──────────┐              │
│   │   DONE   │      │ DETACHED │      │ std::    │              │
│   │  (safe)  │      │  (runs   │      │ terminate│              │
│   │          │      │  alone)  │      │  called  │              │
│   └──────────┘      └──────────┘      └──────────┘              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

⚠️ CRITICAL RULE

Một thread PHẢI được join() hoặc detach() trước khi destructor được gọi! Nếu không, chương trình sẽ gọi std::terminate() và crash.


join() vs detach()

join() — Chờ đợi

cpp
#include <iostream>
#include <thread>
#include <chrono>

void longTask() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "Task completed!\n";
}

int main() {
    std::thread t(longTask);
    
    std::cout << "Waiting for thread...\n";
    t.join();  // ⏳ Block cho đến khi thread xong
    
    std::cout << "Thread finished, continuing...\n";
    return 0;
}

Output:

Waiting for thread...
Task completed!        (sau 2 giây)
Thread finished, continuing...

detach() — Fire and Forget

cpp
#include <iostream>
#include <thread>
#include <chrono>

void backgroundTask() {
    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "Background task done!\n";  // ⚠️ Có thể không in!
}

int main() {
    std::thread t(backgroundTask);
    t.detach();  // Thread chạy độc lập
    
    std::cout << "Main continues immediately...\n";
    
    // ⚠️ Nếu main() kết thúc trước thread, output sẽ bị cắt!
    std::this_thread::sleep_for(std::chrono::seconds(4));
    return 0;
}

⚠️ Detach Caveats

  • Thread detached không thể join lại
  • Nếu main() kết thúc, detached threads bị force terminate
  • Không thể biết khi nào thread hoàn thành
  • Use case: Daemon threads, logging, monitoring

Truyền Arguments

By Value (Copy)

cpp
#include <iostream>
#include <thread>
#include <string>

void greet(std::string name, int times) {
    for (int i = 0; i < times; ++i) {
        std::cout << "Hello, " << name << "!\n";
    }
}

int main() {
    std::string myName = "PENALGO";
    
    // Arguments được COPY vào thread
    std::thread t(greet, myName, 3);
    
    myName = "Changed";  // Không ảnh hưởng thread
    
    t.join();
    return 0;
}

By Reference (với std::ref)

cpp
#include <iostream>
#include <thread>
#include <functional>  // std::ref

void increment(int& value) {
    value += 10;
}

int main() {
    int counter = 0;
    
    // ❌ SAI: Sẽ copy, không phải reference
    // std::thread t1(increment, counter);
    
    // ✅ ĐÚNG: Dùng std::ref() để truyền reference
    std::thread t(increment, std::ref(counter));
    t.join();
    
    std::cout << "Counter: " << counter << std::endl;  // 10
    return 0;
}

Passing Member Functions

cpp
#include <iostream>
#include <thread>

class Worker {
public:
    void doWork(int id) {
        std::cout << "Worker " << id << " is working...\n";
    }
};

int main() {
    Worker worker;
    
    // Truyền member function: &Class::method, &object, args...
    std::thread t(&Worker::doWork, &worker, 42);
    t.join();
    
    return 0;
}

Lambda Functions

cpp
#include <iostream>
#include <thread>

int main() {
    int localVar = 100;
    
    // Lambda với capture
    std::thread t([&localVar]() {
        std::cout << "Lambda sees: " << localVar << std::endl;
        localVar = 200;
    });
    
    t.join();
    std::cout << "After thread: " << localVar << std::endl;  // 200
    
    return 0;
}

std::this_thread Utilities

cpp
#include <iostream>
#include <thread>
#include <chrono>

void demoUtilities() {
    // 1. Get thread ID
    std::thread::id myId = std::this_thread::get_id();
    std::cout << "Thread ID: " << myId << std::endl;
    
    // 2. Sleep for duration
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    
    // 3. Sleep until specific time point
    auto wakeTime = std::chrono::steady_clock::now() 
                  + std::chrono::seconds(1);
    std::this_thread::sleep_until(wakeTime);
    
    // 4. Yield — nhường CPU cho thread khác
    std::this_thread::yield();
}

int main() {
    std::cout << "Main thread ID: " 
              << std::this_thread::get_id() << std::endl;
    
    std::thread t(demoUtilities);
    t.join();
    
    return 0;
}

Hardware Concurrency

cpp
#include <iostream>
#include <thread>
#include <vector>

int main() {
    // Số lượng hardware threads (cores/hyperthreads)
    unsigned int numCores = std::thread::hardware_concurrency();
    
    std::cout << "Hardware threads: " << numCores << std::endl;
    
    // Tạo đúng số threads tối ưu
    std::vector<std::thread> threads;
    
    for (unsigned int i = 0; i < numCores; ++i) {
        threads.emplace_back([i]() {
            std::cout << "Thread " << i << " on core\n";
        });
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

💡 Best Practice

Số threads tối ưu thường là hardware_concurrency() cho CPU-bound tasks, hoặc nhiều hơn cho I/O-bound tasks.


RAII Pattern với Thread

Vấn đề: Exception Safety

cpp
void riskyCode() {
    std::thread t(someFunction);
    
    doSomething();  // ⚠️ Nếu throw exception...
    
    t.join();       // ... dòng này không được gọi → CRASH!
}

Giải pháp: Custom RAII Wrapper

cpp
#include <thread>
#include <utility>

class ScopedThread {
    std::thread t_;
    
public:
    explicit ScopedThread(std::thread t) 
        : t_(std::move(t)) {
        if (!t_.joinable()) {
            throw std::logic_error("No thread");
        }
    }
    
    ~ScopedThread() {
        t_.join();  // Tự động join khi ra khỏi scope
    }
    
    // Không cho copy
    ScopedThread(const ScopedThread&) = delete;
    ScopedThread& operator=(const ScopedThread&) = delete;
};

void safeCode() {
    ScopedThread st(std::thread(someFunction));
    
    doSomething();  // Dù có exception, thread vẫn được join!
}

C++20: std::jthread (Auto-joining Thread)

cpp
#include <thread>
#include <iostream>

void task() {
    std::cout << "jthread task running\n";
}

int main() {
    std::jthread jt(task);  // C++20
    
    // Không cần gọi join()!
    // jthread tự động join trong destructor
    
    return 0;
}

Move Semantics với Thread

cpp
#include <thread>
#include <vector>
#include <iostream>

int main() {
    std::thread t1([]() { 
        std::cout << "Thread 1\n"; 
    });
    
    // Threads không thể copy, chỉ có thể move
    // std::thread t2 = t1;  // ❌ Compile error
    
    std::thread t2 = std::move(t1);  // ✅ OK
    
    // t1 giờ "empty", t2 owns the thread
    std::cout << "t1 joinable: " << t1.joinable() << std::endl;  // 0
    std::cout << "t2 joinable: " << t2.joinable() << std::endl;  // 1
    
    t2.join();
    
    // Container của threads
    std::vector<std::thread> threads;
    for (int i = 0; i < 3; ++i) {
        threads.emplace_back([i]() {
            std::cout << "Thread " << i << "\n";
        });
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    return 0;
}

📚 Tổng kết

FeatureCode
Tạo threadstd::thread t(func, args...);
Chờ hoàn thànht.join();
Chạy độc lậpt.detach();
Kiểm tra joinablet.joinable()
Get IDstd::this_thread::get_id()
Sleepstd::this_thread::sleep_for(duration)
Yieldstd::this_thread::yield()
Hardware threadsstd::thread::hardware_concurrency()
Pass by refstd::ref(var)

➡️ Tiếp theo

Bây giờ bạn biết cách tạo threads, hãy xem điều gì xảy ra khi nhiều threads truy cập cùng data...

Race Conditions → — Khi mọi thứ đổ vỡ.