Szukaj…


Składnia

  • wątek()
  • wątek (wątek i inne)
  • wyraźny wątek (Function && func, Args && ... args)

Parametry

Parametr Detale
other Przejmuje własność other , other nie jest już właścicielem wątku
func Funkcja wywoływania w oddzielnym wątku
args Argumenty za func

Uwagi

Niektóre uwagi:

  • Dwa obiekty std::thread nigdy nie mogą reprezentować tego samego wątku.
  • Obiekt std::thread może znajdować się w stanie, w którym nie reprezentuje żadnego wątku (tj. Po ruchu, wywołaniu join itp.).

Operacje na wątkach

Gdy rozpoczniesz wątek, będzie on wykonywany aż do zakończenia.

Często w pewnym momencie musisz (być może - wątek może być już zrobiony) czekać na zakończenie wątku, ponieważ chcesz na przykład użyć wyniku.

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

Możesz także detach wątek, umożliwiając mu swobodne wykonywanie:

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

Przekazywanie odwołania do wątku

Nie możesz przekazać referencji (lub const referencji) bezpośrednio do wątku, ponieważ std::thread skopiuje / przeniesie. Zamiast tego użyj 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

Tworzenie std :: wątku

W C ++ wątki są tworzone przy użyciu klasy std :: thread. Wątek jest osobnym przepływem wykonania; analogicznie jest, gdy pomocnik wykonuje jedno zadanie, podczas gdy ty jednocześnie wykonujesz inne. Gdy cały kod w wątku jest wykonywany, kończy się . Podczas tworzenia wątku musisz przekazać coś do wykonania na nim. Kilka rzeczy, które możesz przekazać do wątku:

  • Darmowe funkcje
  • Funkcje członka
  • Obiekty Functor
  • Wyrażenia lambda

Przykład funkcji bezpłatnej - wykonuje funkcję w osobnym wątku ( przykład na żywo ):

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

Przykład funkcji składowej - wykonuje funkcję składową w osobnym wątku ( przykład na żywo ):

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

Przykład obiektu Functor (przykład na żywo ):

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

Przykład wyrażenia lambda (przykład na żywo ):

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

Operacje na bieżącym wątku

std::this_thread to namespace która ma funkcje do robienia interesujących rzeczy w bieżącym wątku z funkcji, z której jest wywoływany.

Funkcjonować Opis
get_id Zwraca identyfikator wątku
sleep_for Śpi przez określony czas
sleep_until Śpi do określonego czasu
yield Zmień harmonogram uruchomionych wątków, nadając priorytet innym wątkom

Pobieranie bieżącego wątku za pomocą 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

Spanie przez 3 sekundy za pomocą 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";

Spanie do 3 godzin w przyszłości za pomocą 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";

Zezwalanie innym wątkom na pierwszeństwo przy użyciu 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();

Używanie std :: async zamiast std :: thread

std::async jest także w stanie tworzyć wątki. W porównaniu do std::thread jest uważany za mniej wydajny, ale łatwiejszy w użyciu, gdy chcesz tylko uruchomić funkcję asynchronicznie.

Asynchroniczne wywoływanie funkcji

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

Typowe pułapki

  • Funkcja std::async zwraca wartość std::future która zawiera wartość zwracaną, która zostanie obliczona przez funkcję. Gdy ta future zostanie zniszczona, czeka na zakończenie wątku, dzięki czemu Twój kod będzie efektywnie jednowątkowy. Można to łatwo przeoczyć, gdy nie potrzebujesz wartości zwracanej:

    std::async(std::launch::async, square, 5);
    //thread already completed at this point, because the returning future got destroyed
    
  • std::async działa bez zasad uruchamiania, więc std::async(square, 5); kompiluje. Gdy to zrobisz, system może zdecydować, czy chce utworzyć wątek, czy nie. Pomysł polegał na tym, że system decyduje się na utworzenie wątku, chyba że działa już na nim więcej wątków, niż może działać wydajnie. Niestety implementacje zwykle po prostu nie wybierają tworzenia wątku w takiej sytuacji, więc musisz zastąpić to zachowanie za pomocą std::launch::async która zmusza system do utworzenia wątku.

  • Uważaj na warunki wyścigu.

Więcej o asynchronizacji kontraktów futures i obietnic

Zapewnienie, że wątek jest zawsze łączony

Kiedy destruktora std::thread jest wywoływany, wywołanie albo join() lub detach() muszą być wykonane. Jeśli wątek nie został przyłączony ani odłączony, domyślnie zostanie wywołane std::terminate . Przy użyciu RAII jest to na ogół dość proste do osiągnięcia:

class thread_joiner
{
public:

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

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

private:

    std::thread t_;
}

Jest to następnie używane w następujący sposób:

 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

Zapewnia to także wyjątkowe bezpieczeństwo; gdybyśmy utworzyli nasz wątek normalnie, a praca wykonana w t() wykonująca inne obliczenia spowodowałaby wyjątek, funkcja join() nigdy nie zostałaby wywołana w naszym wątku, a nasz proces zostałby zakończony.

Ponowne przypisywanie obiektów wątków

Możemy tworzyć puste obiekty wątków i przypisywać im później pracę.

Jeśli przypiszemy obiekt wątku do innego aktywnego, joinable wątku, std::terminate zostanie automatycznie wywołane przed zastąpieniem wątku.

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

Podstawowa synchronizacja

Synchronizację wątków można osiągnąć za pomocą muteksów, między innymi operacjami podstawowymi synchronizacji. Standardowa biblioteka udostępnia kilka typów mutexów, ale najprostszym jest std::mutex . Aby zablokować muteks, konstruujesz na nim blokadę. Najprostszym typem blokady jest 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

W przypadku std::lock_guard jest zablokowany na cały okres istnienia obiektu blokady. W przypadkach, w których musisz ręcznie kontrolować regiony blokowania, użyj zamiast tego 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
}

Więcej struktur synchronizacji wątków

Używanie zmiennych warunków

Zmienna warunkowa jest prymitywem używanym w połączeniu z muteksem do koordynowania komunikacji między wątkami. Chociaż nie jest to ani wyłączny, ani najbardziej skuteczny sposób na osiągnięcie tego, może być jednym z najprostszych dla osób zaznajomionych ze schematem.

Czeka się na std::unique_lock<std::mutex> std::condition_variable ze std::unique_lock<std::mutex> . Umożliwia to kodowi bezpieczne sprawdzenie stanu współdzielonego przed podjęciem decyzji o kontynuacji akwizycji.

Poniżej znajduje się szkic producenta-konsumenta, który używa std::thread , std::condition_variable , std::mutex i kilku innych, aby uczynić rzeczy interesującymi.

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

Utwórz prostą pulę wątków

Prymitywy wątków w C ++ 11 są nadal stosunkowo niskie. Można ich użyć do napisania konstruktu wyższego poziomu, takiego jak pula wątków:

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; } ) zwraca std::future<std::string> , który gdy obiekt zadań zaczyna działać, jest zapełniany hello world .

tasks.start(10) uruchamiając tasks.start(10) (który uruchamia 10 wątków).

Użycie packaged_task<void()> polega tylko na tym, że nie ma odpowiednika std::function wymazanym typem, który przechowywałby typy tylko ruchowe. Napisanie niestandardowego byłoby prawdopodobnie szybsze niż użycie packaged_task<void()> .

Przykład na żywo .

C ++ 11

W C ++ 11 zamień result_of_t<blah> typename result_of<blah>::type .

Więcej na temat Mutexów .

Magazyn lokalny wątków

Magazyn lokalny wątków można utworzyć za pomocą słowa kluczowego thread_local . Mówi się, że zmienna zadeklarowana za pomocą specyfikatora thread_local ma czas przechowywania wątku.

  • Każdy wątek w programie ma własną kopię każdej zmiennej lokalnej wątku.
  • Zmienna lokalna wątku z zakresem funkcji (lokalnym) zostanie zainicjowana przy pierwszym przejściu kontroli przez jej definicję. Taka zmienna jest domyślnie statyczna, chyba że zadeklarowana jako extern .
  • Zmienna lokalna z obszarem nazw lub zakresem klas (nielokalnym) zostanie zainicjowana w ramach uruchamiania wątku.
  • Zmienne lokalne wątku są niszczone po zakończeniu wątku.
  • Członek klasy może być lokalny dla wątku tylko wtedy, gdy jest statyczny. Będzie zatem jedna kopia tej zmiennej na wątek, a nie jedna kopia na parę (wątek, instancja).

Przykład:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow