Recherche…


Syntaxe

  • fil()
  • thread (thread && autre)
  • thread explicite (Fonction && func, Args && ... args)

Paramètres

Paramètre Détails
other Prend possession de l' other , l' other ne possède plus le fil
func Fonction d'appeler dans un fil séparé
args Arguments pour le func

Remarques

Quelques notes:

  • Deux objets std::thread ne peuvent jamais représenter le même thread.
  • Un objet std::thread peut être dans un état où il ne représente aucun thread (après un déplacement, après avoir appelé join , etc.).

Opérations de filetage

Lorsque vous démarrez un thread, il s'exécute jusqu'à ce qu'il soit terminé.

Souvent, à un moment donné, vous devez (peut-être - le thread peut-être déjà fait) attendre que le thread se termine, car vous souhaitez par exemple utiliser le résultat.

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';

Vous pouvez également detach le thread en le laissant s'exécuter librement:

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

Passer une référence à un fil

Vous ne pouvez pas transmettre une référence (ou une référence const ) directement à un thread car std::thread les copiera / déplacera. Au lieu de cela, utilisez 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

Créer un thread std ::

En C ++, les threads sont créés à l'aide de la classe std :: thread. Un thread est un flux séparé d'exécution. c'est comme si une aide effectuait une tâche pendant que vous exécutiez une autre tâche simultanément. Lorsque tout le code du thread est exécuté, il se termine . Lors de la création d'un thread, vous devez transmettre quelque chose à exécuter. Quelques points à transmettre à un sujet:

  • Fonctions gratuites
  • Fonctions membres
  • Objets foncteur
  • Expressions lambda

Exemple de fonction libre - exécute une fonction sur un thread séparé ( Exemple Live ):

#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;
}

Exemple de fonction membre - exécute une fonction membre sur un thread séparé ( Exemple Live ):

#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;
}

Exemple d'objet Functor ( en direct Exemple ):

#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;
}

Exemple d'expression Lambda ( Exemple Live ):

#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;
}

Opérations sur le thread en cours

std::this_thread est un namespace qui a des fonctions pour faire des choses intéressantes sur le thread en cours depuis la fonction à partir de laquelle il est appelé.

Fonction La description
get_id Renvoie l'id du thread
sleep_for Dort pour une durée déterminée
sleep_until Dort jusqu'à une heure précise
yield Replanifier les threads en cours d'exécution, en donnant la priorité aux autres threads

Obtenir l'ID de thread actuel en utilisant 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

Dormir pendant 3 secondes en utilisant 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";

Dormir jusqu'à 3 heures dans le futur en utilisant 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";

Laisser les autres threads prendre la priorité en utilisant 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();

Utiliser std :: async au lieu de std :: thread

std::async est également capable de créer des threads. Comparé à std::thread il est considéré comme moins puissant mais plus facile à utiliser lorsque vous souhaitez simplement exécuter une fonction de manière asynchrone.

Appel asynchrone d'une fonction

#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
}

Pièges courants

  • std::async renvoie un std::future contenant la valeur de retour qui sera calculée par la fonction. Lorsque ce future est détruit, il attend que le thread se termine, ce qui rend votre code efficacement unique. Ceci est facilement négligé lorsque vous n'avez pas besoin de la valeur de retour:

    std::async(std::launch::async, square, 5);
    //thread already completed at this point, because the returning future got destroyed
    
  • std::async fonctionne sans politique de lancement, donc std::async(square, 5); compile. Lorsque vous faites cela, le système peut décider s'il veut créer un thread ou non. L'idée était que le système choisisse de créer un thread à moins qu'il n'exécute déjà plus de threads qu'il ne peut exécuter efficacement. Malheureusement, les implémentations choisissent généralement de ne pas créer de thread dans cette situation, donc vous devez remplacer ce comportement par std::launch::async ce qui force le système à créer un thread.

  • Attention aux conditions de course.

Plus sur async sur les contrats à terme et les promesses

S'assurer qu'un fil est toujours joint

Lorsque le destructeur de std::thread est appelé, un appel à join() ou detach() doit avoir été effectué. Si un thread n'a pas été joint ou détaché, alors std::terminate sera appelé par défaut. En utilisant RAII , ceci est généralement assez simple à réaliser:

class thread_joiner
{
public:

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

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

private:

    std::thread t_;
}

Ceci est alors utilisé comme ça:

 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

Cela fournit également une sécurité d'exception; Si nous avions créé notre thread normalement et que le travail effectué dans t() effectuant d'autres calculs avait généré une exception, join() n'aurait jamais été appelée sur notre thread et notre processus aurait été terminé.

Réaffectation des objets thread

Nous pouvons créer des objets de threads vides et leur assigner du travail ultérieurement.

Si nous affectons un objet thread à un autre thread actif, joinable , std::terminate sera automatiquement appelé avant le remplacement du thread.

#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);
}

Synchronisation de base

La synchronisation des threads peut être effectuée à l'aide de mutex, parmi d'autres primitives de synchronisation. Il existe plusieurs types de mutex fournis par la bibliothèque standard, mais le plus simple est std::mutex . Pour verrouiller un mutex, vous construisez un verrou. Le type de verrou le plus simple est 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

Avec std::lock_guard le mutex est verrouillé pendant toute la durée de vie de l'objet verrou. Dans les cas où vous devez contrôler manuellement les régions pour le verrouillage, utilisez plutôt 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
}

Plus de structures de synchronisation de threads

Utilisation de variables de condition

Une variable de condition est une primitive utilisée avec un mutex pour orchestrer la communication entre les threads. Bien que ce ne soit ni le moyen exclusif ou le plus efficace pour y parvenir, il peut être parmi les plus simples pour ceux qui connaissent le modèle.

On attend sur une std::condition_variable avec un std::unique_lock<std::mutex> . Cela permet au code d'examiner en toute sécurité l'état partagé avant de décider de procéder ou non à l'acquisition.

Vous trouverez ci-dessous un croquis producteur-consommateur utilisant std::thread , std::condition_variable , std::mutex et quelques autres pour rendre les choses intéressantes.

#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;
}

Créez un pool de threads simple

Les primitives de threading C ++ 11 sont encore relativement peu nombreuses. Ils peuvent être utilisés pour écrire une construction de niveau supérieur, comme un pool de threads:

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; } ) renvoie un std::future<std::string> qui, lorsque l'objet tâche est exécuté, est rempli avec hello world .

Vous créez des threads en exécutant tasks.start(10) (qui démarre 10 threads).

L'utilisation de packaged_task<void()> est simplement due au fait qu'il n'y a pas d'équivalent std::function effacé par type qui stocke les types de déplacement uniquement. Écrire un fichier personnalisé serait probablement plus rapide que d'utiliser packaged_task<void()> .

Exemple en direct .

C ++ 11

En C ++ 11, remplacez result_of_t<blah> par typename result_of<blah>::type .

Plus sur Mutexes .

Stockage local

Le stockage local de thread peut être créé à l'aide du mot clé thread_local . Une variable déclarée avec le spécificateur thread_local est dite avoir une durée de stockage de thread.

  • Chaque thread dans un programme a sa propre copie de chaque variable locale de thread.
  • Une variable locale au thread avec une portée de fonction (locale) sera initialisée lors du premier passage du contrôle dans sa définition. Une telle variable est implicitement statique, sauf si déclarée extern .
  • Une variable locale au thread avec un espace de nommage ou une étendue de classe (non locale) sera initialisée dans le cadre du démarrage du thread.
  • Les variables thread-locales sont détruites à la fin du thread.
  • Un membre d'une classe ne peut être local que s'il est statique. Il y aura donc une copie de cette variable par thread, plutôt qu'une copie par paire (thread, instance).

Exemple:

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow