„Detaillierte Erklärung und Anwendung des Thread-Pools“

Einführung

Thread-Pool ist eine Multi-Threading-Technologie, mit der der Multi-Core-Prozessor des Computers effektiv genutzt werden kann, um die Leistung und Effizienz des Programms zu verbessern. Der Thread-Pool kann mehrere Threads verwalten und planen und Aufgaben inaktiven Threads zur Ausführung zuweisen, wodurch der Mehraufwand durch wiederholtes Erstellen und Zerstören von Threads und die Verschwendung von Systemressourcen durch zu viele Threads vermieden wird.
Der Thread-Pool besteht aus einem Thread-Pool-Manager, Arbeitsthreads und Aufgabenwarteschlangen. Unter der Kontrolle des Managers werden Arbeitsthreads mehrere Aufgaben zur Verarbeitung zugewiesen, wodurch die Leistung und Effizienz des Programms verbessert wird.
Der CPP-Thread-Pool ist eine fortschrittliche Technologie zur Realisierung von Multithread-Programmierung, die die Ausführungseffizienz von Programmen in Umgebungen mit hoher Parallelität verbessern kann.

halten

Wenn alle Aufgaben unbegrenzt ausgeführt werden, verhindert die begrenzte Anzahl von Threads im Thread-Pool, dass Aufgaben rechtzeitig verarbeitet werden, wodurch die Reaktionsgeschwindigkeit und Leistung des Programms verringert werden.
In diesem Fall können Sie erwägen, die Anzahl der Threads im Thread-Pool zu erhöhen, um die gleichzeitige Verarbeitungsfähigkeit des Programms zu erhöhen. Darüber hinaus können spezielle Verarbeitungsmaßnahmen für unendlich ausgeführte Aufgaben ergriffen werden, z. B. geplante Unterbrechungen oder Stapelausführungen, um zu vermeiden, dass Aufgaben Thread-Ressourcen über einen längeren Zeitraum belegen.
Es ist zu beachten, dass eine Erhöhung der Anzahl der Threads im Thread-Pool zwar die gleichzeitige Verarbeitungsfähigkeit des Programms verbessern kann, aber auch zu einem gewissen Verbrauch von Systemressourcen führt. Daher müssen Sie beim Festlegen der Thread-Pool-Größe die verfügbaren Ressourcen des Systems und die tatsächlichen Geschäftsanforderungen umfassend berücksichtigen, um optimale Leistung und Effizienz zu erzielen.

Vollständige Codedetails

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>

class ThreadPool {
    
    
public:
    ThreadPool(size_t threadCount) : m_stop(false) {
    
    
        for (size_t i = 0; i < threadCount; ++i) {
    
    
            m_threads.emplace_back([this]() {
    
     // 利用 lambda 表达式创建线程
                while(true) {
    
    
                    std::unique_lock<std::mutex> lock(m_mutex);
                    m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
                    if (m_stop && m_tasks.empty()) return;`在这里插入代码片`

                    std::function<void()> task = std::move(m_tasks.front()); // 取出任务
                    m_tasks.pop();
                    lock.unlock();
                    
                    task(); // 执行任务
                }
            });
        }
    }
    
    ~ThreadPool() {
    
    
        {
    
    
            std::unique_lock<std::mutex> lock(m_mutex);
            m_stop = true;
        }
        m_cv.notify_all();

        for (auto& thread : m_threads) {
    
    
            thread.join();
        }
    }

    template<class F, class... Args>
    void addTask(F&& f, Args&&... args) {
    
    
        std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
        {
    
    
            std::unique_lock<std::mutex> lock(m_mutex);
            m_tasks.emplace(std::move(task));
        }
        m_cv.notify_one();
    }
    private:
    std::vector<std::thread> m_threads;
    std::queue<std::function<void()>> m_tasks;
    std::mutex m_mutex;
    std::condition_variable m_cv;
    bool m_stop;
    }

Dieser Code implementiert einen einfachen Thread-Pool mit den folgenden Merkmalen:
● Die Anzahl der Threads im Thread-Pool wird durch den Konstruktorparameter angegeben;
● Unterstützt das Hinzufügen von Aufgaben, und Aufgaben sind aufrufbare Objekte;
● Der Thread-Pool wartet darauf, dass alle Aufgaben ausgeführt werden Wird ausgeführt, wenn es zerstört wird. Alle Threads abschließen und stoppen.
● Der Thread-Pool unterstützt mehrere Threads zum gleichzeitigen Aufrufen der addTask-Methode, und der Thread-Pool verwendet Mutexe und Bedingungsvariablen, um eine Thread-Synchronisierung zu erreichen.
Anwendungsbeispiel:

void foo(int n) {
    
    
    std::cout << "Task " << n << " is running in thread " << std::this_thread::get_id() << std::endl;
}

int main() {
    
    
    ThreadPool pool(4);
    for (int i = 0; i < 10; ++i) {
    
    
        pool.addTask(foo, i);
    }
    return 0;
}

In diesem Beispiel wird ein Thread-Pool erstellt und 10 Aufgaben hinzugefügt. Jede Aufgabe besteht darin, einen Text zu drucken. Es ist ersichtlich, dass unterschiedliche Aufgaben zur Ausführung unterschiedlichen Threads zugewiesen werden.

Erkläre im Detail

Thread-Pool ist ein häufig verwendetes Multithread-Programmiermodell. Es weist einer festen Anzahl von Threads mehrere Aufgaben zur Ausführung zu, vermeidet so den Overhead durch häufiges Erstellen und Zerstören von Threads und kann auch die Leistung und Effizienz des Programms erheblich verbessern.

Thread-Pool-Konstruktor

Im Konstruktor müssen wir die Anzahl der Threads im Thread-Pool angeben. Wir verwenden in C++11 verschiedene Vorlagen, um verschiedene Arten von aufrufbaren Objekten zu unterstützen. Im Konstruktor initialisieren wir das Thread-Array und verwenden Lambda-Ausdrücke, um Ausführungsfunktionen für jeden Thread zu erstellen. Die while-Schleife im Lambda-Ausdruck wartet immer auf die Ausführung einer Aufgabe in der Aufgabenwarteschlange und außerdem auf ein Signal, ob der Thread-Pool beendet wurde.

ThreadPool(size_t threadCount) : m_stop(false) {
    
    
    for (size_t i = 0; i < threadCount; ++i) {
    
    
        m_threads.emplace_back([this]() {
    
     // 利用 lambda 表达式创建线程
            while(true) {
    
    
                std::unique_lock<std::mutex> lock(m_mutex);
                m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
                if (m_stop && m_tasks.empty()) return;

                std::function<void()> task = std::move(m_tasks.front()); // 取出任务
                m_tasks.pop();
                lock.unlock();
                
                task(); // 执行任务
            }
        });
    }
}

Thread-Pool-Destruktor

Im Destruktor müssen wir die Methode „join()“ aufrufen, um darauf zu warten, dass alle Threads beendet werden und Ressourcen freigeben. Gleichzeitig müssen wir auch das Signal m_stop auf true setzen, damit die Ausführungsfunktion des Thread-Pools beendet werden kann, wenn die Aufgabenwarteschlange leer ist.

~ThreadPool() {
    
    
    {
    
    
        std::unique_lock<std::mutex> lock(m_mutex);
        m_stop = true;
    }
    m_cv.notify_all();

    for (auto& thread : m_threads) {
    
    
        thread.join();
    }
}

Aufgabenfunktion hinzufügen

Die Funktion addTask() ist eine Funktion, die Aufgaben zum Thread-Pool hinzufügt. Es verwendet verschiedene Vorlagen, um verschiedene Arten von aufrufbaren Objekten zu unterstützen. Zuerst muss das aufrufbare Objekt in eine Aufgabe vom Typ function<void()> konvertiert werden, dann wird die Aufgabe zur Aufgabenwarteschlange hinzugefügt und ein wartender Thread wird benachrichtigt, damit er die Aufgabe ausführen kann.

1template<class F, class... Args>
2void addTask(F&& f, Args&&... args) {
    
    
3    std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
4    {
    
    
5        std::unique_lock<std::mutex> lock(m_mutex);
6        m_tasks.emplace(std::move(task));
7    }
8    m_cv.notify_one();
9}

Aufgabenwarteschlange und Synchronisierungsmechanismus

Die Aufgabenwarteschlange und der Synchronisierungsmechanismus sind der Kern der Thread-Pool-Implementierung. In der Aufgabenwarteschlange verwenden wir den Typ std::queue<std::function<void()>> zum Speichern von Aufgaben, wobei std::function<void()> den Typ eines beliebigen aufrufbaren Objekts darstellt. Da sich mehrere Threads die Aufgabenwarteschlange teilen, müssen wir einen Mutex (m_mutex) und eine Bedingungsvariable (m_cv) verwenden, um eine Thread-Synchronisierung zu erreichen.
1std::queue<std::function<void()>> m_tasks;
2std::mutex m_mutex;
3std::condition_variable m_cv;
In der Ausführungsfunktion verwenden wir die Methode wait() der Bedingungsvariablen m_cv, um auf die zu warten Aufgabenwarteschlange Es müssen Aufgaben ausgeführt werden. Wenn eine neue Aufgabe hinzugefügt oder der Thread-Pool beendet wird, verwenden wir die Methode notify_one(), um den wartenden Thread aufzuwecken. Wenn wir eine Aufgabe ausführen, müssen wir gleichzeitig die Aufgabe zuerst aus der Aufgabenwarteschlange entfernen, um zu vermeiden, dass mehrere Threads dieselbe Aufgabe gleichzeitig ausführen.

std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [this](){
    
     return m_stop || !m_tasks.empty(); }); // 等待任务或终止信号
if (m_stop && m_tasks.empty()) return;

Ein Wrapper für aufrufbare Objekte

Beim Hinzufügen von Aufgaben müssen wir das aufrufbare Objekt umschließen, damit verschiedene Arten von aufrufbaren Objekten in Aufgaben vom Typ function<void()> konvertiert werden können. Hier verwenden wir die Methode std::bind(), um die Bindung aufrufbarer Objekte zu implementieren, und verwenden std::forward(f) und std::forward(args)..., um Parameter weiterzuleiten.

template<class F, class... Args>
void addTask(F&& f, Args&&... args) {
    
    
    std::function<void()> task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);
    {
    
    
        std::unique_lock<std::mutex> lock(m_mutex);
        m_tasks.emplace(std::move(task));
    }
    m_cv.notify_one();
}

Zusammenfassen

Der obige Code implementiert einen einfachen Thread-Pool, der Entwicklern helfen kann, die Multi-Core-CPU des Computers besser zu nutzen, um die Programmleistung und -effizienz zu verbessern. Bei der Implementierung des Thread-Pools muss auf das Problem der Thread-Synchronisierung geachtet werden. Die Thread-Synchronisierung kann einfach mithilfe von std::mutex und std::condition_variable erreicht werden. Achten Sie gleichzeitig darauf, darauf zu warten, dass alle Aufgaben im Destruktor abgeschlossen sind, um Ressourcenlecks zu vermeiden.

Ich denke du magst

Origin blog.csdn.net/yiyu20180729/article/details/130730565
Empfohlen
Rangfolge