Friday, December 1, 2023

C++ Thread vs Asych Thread

std::thread 

Normal threads are the traditional way to create concurrent threads in C++. 

They are created explicitly using the std::thread class. The programmer is responsible for managing the thread's lifecycle, including starting, joining, and destroying the thread.
Normal threads also do not return a value, so the programmer must use some other mechanism, such as shared memory or inter-thread communication, to communicate the results of the thread's execution.

std::async

Asynchronous threads are a newer way to create concurrent threads in C++. They are created implicitly using the std::async function. The std::async function returns a std::future object,

which can be used to get the result of the thread's execution.

Asynchronous threads are automatically managed by the system, so the programmer does not need to worry about starting, joining, or destroying the thread.

FeatureNormal threadAsynchronous thread
ProsMore powerful and flexibleEasier to use, more efficient for I/O-bound tasks
ConsMore complex to use, less efficient for I/O-bound tasksRequires std::future to get the result

FeatureNormal threadAsynchronous thread
Execution modelSynchronousAsynchronous
Thread creationExplicitImplicit
Thread managementManualAutomatic
Return valueNoneFuture
Error handlingManualAutomatic
Use casesLong-running tasks, CPU-bound tasksI/O-bound tasks, short-lived tasks


#include <iostream> #include <future> using namespace std; int double_number(int n) { return n * 2; } int main() { // Create an asynchronous thread that calls the double_number function future<int> future = async(double_number, 10); // Do some other work while the asynchronous thread is running cout << "Doing some other work..." << endl; // Get the result of the asynchronous thread int result = future.get(); cout << "The result is: " << result << endl; return 0; }

Asych thread pool example 1

  • Avoid blocking the main thread
  • Queue tasks
  • Perform tasks independently 
#include <iostream>
#include <vector>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <deque>

class ThreadPool {
public:
    ThreadPool(size_t numThreads);
    ~ThreadPool();

    void enqueue(std::function<void()> task);

private:
    // Thread pool worker function
    void workerThread();

    // Data members
    std::vector<std::thread> workers;
    std::deque<std::function<void()>> tasks;

    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop;
};

// Constructor: Create worker threads
ThreadPool::ThreadPool(size_t numThreads) : stop(false) {
    for (size_t i = 0; i < numThreads; ++i) {
        workers.emplace_back([this] { workerThread(); });
    }
}

// Destructor: Stop worker threads
ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        stop = true;
    }

    condition.notify_all();

    for (std::thread& worker : workers) {
        worker.join();
    }
}

// Worker thread function
void ThreadPool::workerThread() {
    while (true) {
        std::function<void()> task;

        {
            std::unique_lock<std::mutex> lock(queueMutex);
            condition.wait(lock, [this] { return stop || !tasks.empty(); });

            if (stop && tasks.empty()) {
                return;
            }

            task = std::move(tasks.front());
            tasks.pop_front();
        }

        task(); // Perform the task
    }
}

// Enqueue a task for the thread pool to execute asynchronously
void ThreadPool::enqueue(std::function<void()> task) {
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        tasks.emplace_back(std::move(task));
    }

    condition.notify_one(); // Notify conditional wait when tasks available in queue
}

// Example usage
void exampleTask(int arg) {
    std::cout << "Task executed with argument: " << arg << " by Thread " << std::this_thread::get_id() << std::endl;
}

int main() {
    const size_t numThreads = 4;
    ThreadPool threadPool(numThreads);

    // Enqueue tasks
    for (int i = 0; i < 8; ++i) {
        threadPool.enqueue([i] { exampleTask(i); });
    }

    // Continue with other tasks in the main thread without waiting for the pool
    for (int i = 0; i < 5; ++i) {
        std::cout << "Main thread task: " << i << std::endl;
    }

    // Sleep for a while to allow threads to execute tasks
    std::this_thread::sleep_for(std::chrono::seconds(2));

    return 0;
}

    Asych thread pool example 2

  • Avoid blocking the main thread
  • Queue tasks
  • Perform tasks independently 
  • Limit the maximum number of concurrently running threads to 10.
  • Other new tasks will be queued until a free thread is available
#include <iostream> #include <vector> #include <future> #include <deque> #include <functional> #include <chrono> #include <thread> class ThreadPool { public: ThreadPool(size_t maxThreads); ~ThreadPool(); void enqueue(std::function<void()> task); private: // Thread pool worker function void workerThread(); // Data members std::deque<std::packaged_task<void()>> tasks; std::vector<std::future<void>> futures; size_t maxThreads; size_t activeThreads; }; // Constructor: Create worker threads ThreadPool::ThreadPool(size_t maxThreads) : maxThreads(maxThreads), activeThreads(0) { for (size_t i = 0; i < maxThreads; ++i) { futures.emplace_back(std::async(std::launch::async, [this] { workerThread(); })); } } // Destructor: Stop worker threads ThreadPool::~ThreadPool() { // No need to explicitly stop threads when using std::async } // Worker thread function void ThreadPool::workerThread() { while (true) { std::packaged_task<void()> task; { std::unique_lock<std::mutex> lock(queueMutex); condition.wait(lock, [this] { return tasks.empty(); }); if (tasks.empty()) { return; } task = std::move(tasks.front()); tasks.pop_front(); ++activeThreads; } task(); { std::unique_lock<std::mutex> lock(queueMutex); --activeThreads; } } } // Enqueue a task for the thread pool to execute asynchronously void ThreadPool::enqueue(std::function<void()> task) { { std::unique_lock<std::mutex> lock(queueMutex); tasks.emplace_back(std::packaged_task<void()>(std::move(task))); } condition.notify_one(); } // Example usage void exampleTask(int arg) { std::cout << "Task executed with argument: " << arg << " by Thread " << std::this_thread::get_id() << std::endl; } int main() { const size_t maxThreads = 10; ThreadPool threadPool(maxThreads); // Enqueue tasks for (int i = 0; i < 15; ++i) { threadPool.enqueue([i] { exampleTask(i); }); } // Continue with other tasks in the main thread without waiting for the pool for (int i = 0; i < 5; ++i) { std::cout << "Main thread task: " << i << std::endl; } // Sleep for a while to allow threads to execute tasks std::this_thread::sleep_for(std::chrono::seconds(2)); return 0; }

No comments:

Post a Comment

LeetCode C++ Cheat Sheet June

🎯 Core Patterns & Representative Questions 1. Arrays & Hashing Two Sum – hash map → O(n) Contains Duplicate , Product of A...