Suche…


Syntax

  • Faden()
  • Thread (Thread && Sonstiges)
  • expliziter Thread (Funktion && func, Args && ... args)

Parameter

Parameter Einzelheiten
other Übernimmt den Besitz other , other besitzt den Thread nicht mehr
func Funktion zum Aufrufen eines separaten Threads
args Argumente für func

Bemerkungen

Einige Notizen:

  • Zwei std::thread Objekte können niemals denselben Thread darstellen.
  • Ein std::thread Objekt kann sich in einem Zustand befinden, in dem es keinen Thread darstellt (dh nach einer Verschiebung, nach dem Aufruf von join usw.).

Thread-Operationen

Wenn Sie einen Thread starten, wird er ausgeführt, bis er fertig ist.

An einem bestimmten Punkt müssen Sie (möglicherweise - der Thread ist möglicherweise bereits fertiggestellt) auf das Ende des Threads warten, da Sie beispielsweise das Ergebnis verwenden möchten.

int n;
std::thread thread{ calculateSomething, std::ref(n) };

//Doing some other stuff

//We need 'n' now!
//Wait for the thread to finish - if it is not already done
thread.join();

//Now 'n' has the result of the calculation done in the seperate thread
std::cout << n << '\n';

Sie können den Thread auch detach , damit er frei ausgeführt werden kann:

std::thread thread{ doSomething };

//Detaching the thread, we don't need it anymore (for whatever reason)
thread.detach();

//The thread will terminate when it is done, or when the main thread returns

Übergabe einer Referenz an einen Thread

Sie können einen Verweis (oder einen const Verweis) nicht direkt an einen Thread übergeben, da sie von std::thread kopiert / verschoben werden. Verwenden Sie stattdessen std::reference_wrapper :

void foo(int& b)
{
    b = 10;
}

int a = 1;
std::thread thread{ foo, std::ref(a) }; //'a' is now really passed as reference

thread.join();
std::cout << a << '\n'; //Outputs 10

void bar(const ComplexObject& co)
{
    co.doCalculations();
}

ComplexObject object;
std::thread thread{ bar, std::cref(object) }; //'object' is passed as const&

thread.join();
std::cout << object.getResult() << '\n'; //Outputs the result

Std :: thread erstellen

In C ++ werden Threads mit der Klasse std :: thread erstellt. Ein Thread ist ein separater Ausführungsablauf. Es ist analog zu einem Helfer, der eine Aufgabe ausführt, während Sie gleichzeitig eine andere ausführen. Wenn der gesamte Code im Thread ausgeführt wird, wird er beendet . Beim Erstellen eines Threads müssen Sie etwas übergeben, um darauf ausgeführt zu werden. Einige Dinge, die Sie an einen Thread übergeben können:

  • Freie Funktionen
  • Mitgliedfunktionen
  • Functor-Objekte
  • Lambda-Ausdrücke

Beispiel für eine freie Funktion - Führt eine Funktion in einem separaten Thread aus ( Live-Beispiel ):

#include <iostream>
#include <thread>
 
void foo(int a)
{
    std::cout << a << '\n';
}
 
int main()
{
    // Create and execute the thread
    std::thread thread(foo, 10); // foo is the function to execute, 10 is the
                                 // argument to pass to it
 
    // Keep going; the thread is executed separately
 
    // Wait for the thread to finish; we stay here until it is done
    thread.join();
 
    return 0;
}

Elementfunktion - führt eine Elementfunktion in einem separaten Thread aus ( Live-Beispiel ):

#include <iostream>
#include <thread>
 
class Bar
{
public:
    void foo(int a)
    {
        std::cout << a << '\n';
    }
};
 
int main()
{
    Bar bar;
    
    // Create and execute the thread
    std::thread thread(&Bar::foo, &bar, 10); // Pass 10 to member function
 
    // The member function will be executed in a separate thread
 
    // Wait for the thread to finish, this is a blocking operation
    thread.join();
 
    return 0;
}

Functor-Objektbeispiel ( Live-Beispiel ):

#include <iostream>
#include <thread>
 
class Bar
{
public:
    void operator()(int a)
    {
        std::cout << a << '\n';
    }
};
 
int main()
{
    Bar bar;
    
    // Create and execute the thread
    std::thread thread(bar, 10); // Pass 10 to functor object
 
    // The functor object will be executed in a separate thread
 
    // Wait for the thread to finish, this is a blocking operation
    thread.join();
 
    return 0;
}

Lambda-Ausdruckbeispiel ( Live-Beispiel ):

#include <iostream>
#include <thread>
 
int main()
{
    auto lambda = [](int a) { std::cout << a << '\n'; };

    // Create and execute the thread
    std::thread thread(lambda, 10); // Pass 10 to the lambda expression
 
    // The lambda expression will be executed in a separate thread
 
    // Wait for the thread to finish, this is a blocking operation
    thread.join();
 
    return 0;
}

Vorgänge im aktuellen Thread

std::this_thread ist ein namespace der Funktionen für den aktuellen Thread aus Funktionen enthält, aus denen er aufgerufen wird.

Funktion Beschreibung
get_id Gibt die ID des Threads zurück
sleep_for Schläft für eine bestimmte Zeitspanne
sleep_until Schläft bis zu einer bestimmten Zeit
yield Planen Sie die Ausführung von Threads neu und geben Sie anderen Threads Priorität

std::this_thread::get_id der aktuellen Thread-ID mithilfe von std::this_thread::get_id :

void foo()
{
    //Print this threads id
    std::cout << std::this_thread::get_id() << '\n';
}

std::thread thread{ foo };
thread.join(); //'threads' id has now been printed, should be something like 12556

foo(); //The id of the main thread is printed, should be something like 2420

3 Sekunden lang schlafen mit std::this_thread::sleep_for :

void foo()
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
}

std::thread thread{ foo };
foo.join();

std::cout << "Waited for 3 seconds!\n";

Mit std::this_thread::sleep_until bis 3 Stunden in der Zukunft std::this_thread::sleep_until :

void foo()
{
    std::this_thread::sleep_until(std::chrono::system_clock::now() + std::chrono::hours(3));
}

std::thread thread{ foo };
thread.join();

std::cout << "We are now located 3 hours after the thread has been called\n";

Lassen Sie andere Threads Priorität mit std::this_thread::yield :

void foo(int a)
{
    for (int i = 0; i < al ++i)
        std::this_thread::yield(); //Now other threads take priority, because this thread
                                   //isn't doing anything important

    std::cout << "Hello World!\n";
}

std::thread thread{ foo, 10 };
thread.join();

Verwenden von std :: async anstelle von std :: thread

std::async auch Threads std::async . Im Vergleich zu std::thread wird dies als weniger leistungsfähig betrachtet, aber einfacher zu verwenden, wenn Sie eine Funktion nur asynchron ausführen möchten.

Funktion asynchron aufrufen

#include <future>
#include <iostream>

unsigned int square(unsigned int i){
    return i*i;
}

int main() {
    auto f = std::async(std::launch::async, square, 8);
    std::cout << "square currently running\n"; //do something while square is running
    std::cout << "result is " << f.get() << '\n'; //getting the result from square
}

Häufige Fehler

  • std::async gibt ein std::future , das den Rückgabewert enthält, der von der Funktion berechnet wird. Wenn diese future zerstört wird, wird gewartet, bis der Thread abgeschlossen ist, wodurch der Code effektiv zu einem Thread wird. Dies wird leicht übersehen, wenn Sie den Rückgabewert nicht benötigen:

    std::async(std::launch::async, square, 5);
    //thread already completed at this point, because the returning future got destroyed
    
  • std::async funktioniert ohne std::async , also std::async(square, 5); kompiliert. Wenn Sie dies tun, kann das System entscheiden, ob es einen Thread erstellen möchte oder nicht. Die Idee war, dass das System einen Thread erstellt, es sei denn, es werden bereits mehr Threads ausgeführt, als effizient ausgeführt werden können. Leider entscheiden sich Implementierungen in der Regel einfach dafür, in dieser Situation keinen Thread zu erstellen. std::launch::async müssen Sie dieses Verhalten mit std::launch::async überschreiben, wodurch das System einen Thread erstellen muss.

  • Hüte dich vor den Rennbedingungen.

Mehr über async bei Futures und Promises

Sicherstellen, dass ein Thread immer verbunden ist

Wenn der Destruktor für std::thread aufgerufen wird, um entweder ein Anruf join() oder detach() gemacht worden sein. Wenn ein Thread nicht verbunden oder getrennt wurde, wird standardmäßig std::terminate aufgerufen. Mit RAII ist dies im Allgemeinen einfach genug:

class thread_joiner
{
public:

    thread_joiner(std::thread t)
        : t_(std::move(t))
    { }

    ~thread_joiner()
    {
        if(t_.joinable()) {
            t_.join();
        }
    }

private:

    std::thread t_;
}

Dies wird dann wie folgt verwendet:

 void perform_work()
 {
     // Perform some work
 }

 void t()
 {
     thread_joiner j{std::thread(perform_work)};
     // Do some other calculations while thread is running
 } // Thread is automatically joined here

Dies bietet auch eine Ausnahmesicherheit. Wenn wir unseren Thread normalerweise erstellt hätten und die in t() Arbeit bei der Ausführung anderer Berechnungen eine Ausnahme ausgelöst hätte, wäre join() niemals für unseren Thread aufgerufen worden und unser Prozess wäre beendet worden.

Thread-Objekte neu zuordnen

Wir können leere Thread-Objekte erstellen und ihnen später Arbeit zuweisen.

Wenn wir ein joinable einem anderen aktiven, joinable Thread joinable , wird std::terminate automatisch aufgerufen, bevor der Thread ersetzt wird.

#include <thread>

void foo()
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
}
//create 100 thread objects that do nothing
std::thread executors[100];

// Some code

// I want to create some threads now

for (int i = 0;i < 100;i++)
{
    // If this object doesn't have a thread assigned
    if (!executors[i].joinable())
         executors[i] = std::thread(foo);
}

Grundlegende Synchronisation

Thread-Synchronisierung kann unter Verwendung von Mutexen unter anderen Synchronisationsgrundelementen erreicht werden. Es gibt verschiedene Mutex-Typen, die von der Standardbibliothek bereitgestellt werden, aber der einfachste ist std::mutex . Um einen Mutex zu sperren, konstruieren Sie eine Sperre für ihn. Der einfachste Sperrentyp ist std::lock_guard :

std::mutex m;
void worker() {
    std::lock_guard<std::mutex> guard(m); // Acquires a lock on the mutex
    // Synchronized code here
} // the mutex is automatically released when guard goes out of scope

Bei std::lock_guard der Mutex für die gesamte Lebensdauer des std::lock_guard gesperrt. std::unique_lock Sie die Bereiche für das Sperren manuell steuern müssen, verwenden std::unique_lock stattdessen std::unique_lock :

std::mutex m;
void worker() {
    // by default, constructing a unique_lock from a mutex will lock the mutex
    // by passing the std::defer_lock as a second argument, we
    // can construct the guard in an unlocked state instead and
    // manually lock later.
    std::unique_lock<std::mutex> guard(m, std::defer_lock);
    // the mutex is not locked yet!
    guard.lock();
    // critical section
    guard.unlock();
    // mutex is again released
}

Weitere Thread-Synchronisationsstrukturen

Bedingungsvariablen verwenden

Eine Bedingungsvariable ist ein Grundelement, das in Verbindung mit einem Mutex verwendet wird, um die Kommunikation zwischen Threads zu orchestrieren. Dies ist zwar weder der ausschließliche noch der effizienteste Weg, aber für diejenigen, die mit dem Muster vertraut sind, kann es eines der einfachsten sein.

Man wartet auf eine std::condition_variable mit einem std::unique_lock<std::mutex> . Dadurch kann der Code den gemeinsam genutzten Zustand sicher prüfen, bevor er entscheidet, ob er mit der Erfassung fortfahren möchte oder nicht.

Nachfolgend finden Sie eine Hersteller-Konsumentenskizze, die std::thread , std::condition_variable , std::mutex und einige andere verwendet, um die Dinge interessant zu machen.

#include <condition_variable>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <queue>
#include <random>
#include <thread>


int main()
{
    std::condition_variable cond;
    std::mutex mtx;
    std::queue<int> intq;
    bool stopped = false;

    std::thread producer{[&]()
    {
        // Prepare a random number generator.
        // Our producer will simply push random numbers to intq.
        //
        std::default_random_engine gen{};
        std::uniform_int_distribution<int> dist{};

        std::size_t count = 4006;    
        while(count--)
        {    
            // Always lock before changing
            // state guarded by a mutex and
            // condition_variable (a.k.a. "condvar").
            std::lock_guard<std::mutex> L{mtx};

            // Push a random int into the queue
            intq.push(dist(gen));

            // Tell the consumer it has an int
            cond.notify_one();
        }

        // All done.
        // Acquire the lock, set the stopped flag,
        // then inform the consumer.
        std::lock_guard<std::mutex> L{mtx};

        std::cout << "Producer is done!" << std::endl;

        stopped = true;
        cond.notify_one();
    }};

    std::thread consumer{[&]()
    {
        do{
            std::unique_lock<std::mutex> L{mtx};
            cond.wait(L,[&]()
            {
                // Acquire the lock only if
                // we've stopped or the queue
                // isn't empty
                return stopped || ! intq.empty();
            });

            // We own the mutex here; pop the queue
            // until it empties out.

            while( ! intq.empty())
            {
                const auto val = intq.front();
                intq.pop();

                std::cout << "Consumer popped: " << val << std::endl;
            }

            if(stopped){
                // producer has signaled a stop
                std::cout << "Consumer is done!" << std::endl;
                break;
            }

        }while(true);
    }};

    consumer.join();
    producer.join();
    
    std::cout << "Example Completed!" << std::endl;

    return 0;
}

Erstellen Sie einen einfachen Thread-Pool

C ++ 11-Threading-Grundelemente sind noch relativ niedrig. Sie können verwendet werden, um ein übergeordnetes Konstrukt wie einen Thread-Pool zu schreiben:

C ++ 14
struct tasks {
  // the mutex, condition variable and deque form a single
  // thread-safe triggered queue of tasks:
  std::mutex m;
  std::condition_variable v;
  // note that a packaged_task<void> can store a packaged_task<R>:
  std::deque<std::packaged_task<void()>> work;

  // this holds futures representing the worker threads being done:
  std::vector<std::future<void>> finished;

  // queue( lambda ) will enqueue the lambda into the tasks for the threads
  // to use.  A future of the type the lambda returns is given to let you get
  // the result out.
  template<class F, class R=std::result_of_t<F&()>>
  std::future<R> queue(F&& f) {
    // wrap the function object into a packaged task, splitting
    // execution from the return value:
    std::packaged_task<R()> p(std::forward<F>(f));

    auto r=p.get_future(); // get the return value before we hand off the task
    {
      std::unique_lock<std::mutex> l(m);
      work.emplace_back(std::move(p)); // store the task<R()> as a task<void()>
    }
    v.notify_one(); // wake a thread to work on the task

    return r; // return the future result of the task
  }

  // start N threads in the thread pool.
  void start(std::size_t N=1){
    for (std::size_t i = 0; i < N; ++i)
    {
      // each thread is a std::async running this->thread_task():
      finished.push_back(
        std::async(
          std::launch::async,
          [this]{ thread_task(); }
        )
      );
    }
  }
  // abort() cancels all non-started tasks, and tells every working thread
  // stop running, and waits for them to finish up.
  void abort() {
    cancel_pending();
    finish();
  }
  // cancel_pending() merely cancels all non-started tasks:
  void cancel_pending() {
    std::unique_lock<std::mutex> l(m);
    work.clear();
  }
  // finish enques a "stop the thread" message for every thread, then waits for them:
  void finish() {
    {
      std::unique_lock<std::mutex> l(m);
      for(auto&&unused:finished){
        work.push_back({});
      }
    }
    v.notify_all();
    finished.clear();
  }
  ~tasks() {
    finish();
  }
private:
  // the work that a worker thread does:
  void thread_task() {
    while(true){
      // pop a task off the queue:
      std::packaged_task<void()> f;
      {
        // usual thread-safe queue code:
        std::unique_lock<std::mutex> l(m);
        if (work.empty()){
          v.wait(l,[&]{return !work.empty();});
        }
        f = std::move(work.front());
        work.pop_front();
      }
      // if the task is invalid, it means we are asked to abort:
      if (!f.valid()) return;
      // otherwise, run the task:
      f();
    }
  }
};

tasks.queue( []{ return "hello world"s; } ) gibt ein std::future<std::string> , das beim Ausführen des Aufgabenobjekts mit hello world .

Sie erstellen Threads, indem Sie tasks.start(10) (wodurch 10 Threads tasks.start(10) ).

Die Verwendung von packaged_task<void()> ist lediglich darauf zurückzuführen, dass es keine mit dem Typ std::function äquivalente std::function , die Nur-Move-Typen speichert. Das Erstellen einer benutzerdefinierten Datei wäre wahrscheinlich schneller als die Verwendung von packaged_task<void()> .

Live-Beispiel .

C ++ 11

Ersetzen result_of_t<blah> in C ++ 11 result_of_t<blah> durch den typename result_of<blah>::type .

Mehr zu Mutexes .

Thread-lokaler Speicher

Thread-lokaler Speicher kann mit dem Schlüsselwort thread_local erstellt werden. Eine mit dem thread_local deklarierte Variable hat eine Thread-Speicherdauer.

  • Jeder Thread in einem Programm verfügt über eine eigene Kopie jeder lokalen Threadvariablen.
  • Eine lokale Thread-Variable mit dem Funktionsbereich (local) wird initialisiert, wenn die Steuerung zum ersten Mal ihre Definition durchläuft. Eine solche Variable ist implizit statisch, sofern sie nicht extern deklariert wird.
  • Eine Thread-lokale Variable mit Namespace oder Klassenbereich (nicht lokal) wird beim Start des Threads initialisiert.
  • Threadlokale Variablen werden bei Beendigung des Threads zerstört.
  • Ein Member einer Klasse kann nur dann threadlokal sein, wenn es statisch ist. Es gibt daher eine Kopie dieser Variablen pro Thread und nicht eine Kopie pro Paar (Thread, Instanz).

Beispiel:

void debug_counter() {
    thread_local int count = 0;
    Logger::log("This function has been called %d times by this thread", ++count);
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow