Zoeken…


Syntaxis

  • draad()
  • thread (thread && other)
  • expliciete thread (functie && func, Args && ... args)

parameters

Parameter Details
other Neemt eigendom van other , other bezitten de draad niet meer
func Functie om een afzonderlijke thread in te schakelen
args Argumenten voor func

Opmerkingen

Enkele opmerkingen:

  • Twee std::thread objecten kunnen nooit dezelfde thread vertegenwoordigen.
  • Een std::thread object kan in een status zijn waarin het geen thread vertegenwoordigt (dat wil zeggen na een verplaatsing, na het aanroepen van join , etc.).

Thread operaties

Wanneer u een thread start, wordt deze uitgevoerd totdat deze is voltooid.

Vaak moet u op een bepaald moment (mogelijk - de thread is al klaar) wachten tot de thread is voltooid, omdat u bijvoorbeeld het resultaat wilt gebruiken.

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

U kunt de draad ook detach vrij laten uitvoeren:

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

Een verwijzing doorgeven aan een thread

U kunt een verwijzing (of const verwijzing) niet rechtstreeks aan een thread doorgeven, omdat std::thread zal kopiëren / verplaatsen. Gebruik in plaats daarvan 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

Een std :: thread maken

In C ++ worden threads gemaakt met de klasse std :: thread. Een thread is een afzonderlijke uitvoeringsstroom; het is analoog aan het laten uitvoeren van een taak door een helper terwijl u tegelijkertijd een andere uitvoert. Wanneer alle code in de thread wordt uitgevoerd, wordt deze beëindigd . Wanneer u een thread maakt, moet u iets doorgeven om erop te worden uitgevoerd. Een paar dingen die u kunt doorgeven aan een thread:

  • Gratis functies
  • Lid functies
  • Functor objecten
  • Lambda-uitdrukkingen

Voorbeeld van gratis functie - voert een functie uit op een afzonderlijke thread ( live voorbeeld ):

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

Voorbeeld van lidfunctie - voert een lidfunctie uit op een afzonderlijke thread ( live voorbeeld ):

#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-objectvoorbeeld ( live voorbeeld ):

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

Voorbeeld van lambda-expressie ( live voorbeeld ):

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

Bewerkingen op de huidige thread

std::this_thread is een namespace die functies heeft om interessante dingen te doen op de huidige thread van de functie waar het vandaan komt.

Functie Beschrijving
get_id Retourneert de id van de thread
sleep_for Slaap voor een bepaalde tijd
sleep_until Slaapt tot een bepaald tijdstip
yield Stel lopende threads opnieuw in en geef prioriteit aan andere threads

De huidige threads-ID std::this_thread::get_id met 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

Slaap 3 seconden met 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";

Slapen tot 3 uur in de toekomst met 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";

Laat andere threads prioriteit krijgen met 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();

Met behulp van std :: async in plaats van std :: thread

std::async kan ook threads maken. In vergelijking met std::thread wordt het beschouwd als minder krachtig maar gemakkelijker te gebruiken wanneer u een functie asynchroon wilt uitvoeren.

Asynchroon een functie aanroepen

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

Gemeenschappelijke valkuilen

  • std::async retourneert een std::future met de retourwaarde die wordt berekend door de functie. Wanneer die future wordt vernietigd, wacht deze totdat de thread is voltooid, waardoor uw code effectief single threaded is. Dit wordt gemakkelijk over het hoofd gezien als u de retourwaarde niet nodig hebt:

    std::async(std::launch::async, square, 5);
    //thread already completed at this point, because the returning future got destroyed
    
  • std::async werkt zonder een startbeleid, dus std::async(square, 5); compileert. Wanneer u dat doet, bepaalt het systeem of het een thread wil maken of niet. Het idee was dat het systeem ervoor kiest een thread te maken, tenzij er al meer threads worden uitgevoerd dan efficiënt kan worden uitgevoerd. Helaas kiezen implementaties er meestal gewoon voor om in die situatie nooit een thread te maken, dus je moet dat gedrag overschrijven met std::launch::async waardoor het systeem wordt gedwongen een thread te maken.

  • Pas op voor raceomstandigheden.

Meer over async over futures en beloften

Ervoor zorgen dat een thread altijd is verbonden

Wanneer de destructor voor std::thread wordt aangevoerd, een oproep ofwel join() of detach() moeten getroffen zijn. Als een thread niet is verbonden of losgemaakt, wordt standaard std::terminate aangeroepen. Met RAII is dit over het algemeen eenvoudig genoeg om te bereiken:

class thread_joiner
{
public:

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

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

private:

    std::thread t_;
}

Dit wordt dan zo gebruikt:

 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

Dit biedt ook uitzonderingsveiligheid; als we onze thread normaal hadden gemaakt en het werk dat werd gedaan in t() uitvoeren van andere berekeningen een uitzondering had gegenereerd, zou join() nooit op onze thread zijn aangeroepen en zou ons proces zijn beëindigd.

Thread-objecten opnieuw toewijzen

We kunnen lege thread-objecten maken en er later werk aan toewijzen.

Als we een threadobject toewijzen aan een andere actieve, joinable thread, wordt std::terminate automatisch aangeroepen voordat de thread wordt vervangen.

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

Basis synchronisatie

Threadsynchronisatie kan worden bereikt met behulp van mutexen, naast andere synchronisatieprimitieven. Er zijn verschillende mutex-typen beschikbaar in de standaardbibliotheek, maar de eenvoudigste is std::mutex . Om een mutex te vergrendelen, construeert u er een slot op. Het eenvoudigste std::lock_guard is 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

Met std::lock_guard de mutex vergrendeld voor de hele levensduur van het vergrendelingsobject. In gevallen waarin u de te vergrendelen regio's handmatig moet bedienen, gebruikt u in plaats hiervan 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
}

Meer threadsynchronisatiestructuren

Conditievariabelen gebruiken

Een voorwaardenvariabele is een primitief die wordt gebruikt in combinatie met een mutex om de communicatie tussen threads te orkestreren. Hoewel het noch de exclusieve of meest efficiënte manier is om dit te bereiken, kan het een van de eenvoudigste zijn voor degenen die bekend zijn met het patroon.

Men wacht op een std::condition_variable met een std::unique_lock<std::mutex> . Hierdoor kan de code de gedeelde status veilig onderzoeken voordat wordt besloten of er al dan niet wordt overgegaan tot acquisitie.

Hieronder staat een schets van een producent en consument die std::thread , std::condition_variable , std::mutex en een paar anderen gebruikt om dingen interessant te maken.

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

Maak een eenvoudige thread pool

C ++ 11 threading primitieven zijn nog steeds relatief laag niveau. Ze kunnen worden gebruikt om een construct op een hoger niveau te schrijven, zoals een thread pool:

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; } ) retourneert een std::future<std::string> , die wanneer het takenobject begint te lopen wordt gevuld met hello world .

U maakt threads door tasks.start(10) (waarmee 10 threads worden gestart).

Het gebruik van packaged_task<void()> is alleen omdat er geen type-gewist std::function equivalent is dat alleen-verplaatsbare typen opslaat. Het schrijven van een aangepast exemplaar zou waarschijnlijk sneller zijn dan het gebruik van packaged_task<void()> .

Live voorbeeld .

C ++ 11

Vervang in C ++ 11 result_of_t<blah> door het typename result_of<blah>::type .

Meer over Mutexes .

Thread-lokale opslag

Thread-lokale opslag kan worden gemaakt met behulp van het thread_local sleutelwoord . Van een variabele gedeclareerd met de thread_local specifier wordt gezegd dat deze de opslagduur van de thread heeft.

  • Elke thread in een programma heeft zijn eigen kopie van elke thread-local variabele.
  • Een thread-lokale variabele met functie (lokaal) bereik wordt geïnitialiseerd de eerste keer dat de besturing de definitie doorloopt. Een dergelijke variabele is impliciet statisch, tenzij extern gedeclareerd.
  • Een thread-lokale variabele met naamruimte of klasse (niet-lokaal) bereik wordt geïnitialiseerd als onderdeel van het opstarten van de thread.
  • Thread-lokale variabelen worden vernietigd bij het beëindigen van de thread.
  • Een lid van een klasse kan alleen thread-local zijn als het statisch is. Er is dus één kopie van die variabele per thread, in plaats van één kopie per (thread, instantie) paar.

Voorbeeld:

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow