Sök…


Syntax

  • tråd()
  • tråd (tråd & & annat)
  • explicit tråd (Function && func, Args && ... args)

parametrar

Parameter detaljer
other Tar ägande av other , other äger inte tråden längre
func Funktion för att ringa i en separat tråd
args Argument för func

Anmärkningar

Några anteckningar:

  • Två std::thread kan aldrig representera samma tråd.
  • Ett std::thread objekt kan vara i ett tillstånd där det inte representerar någon tråd (dvs. efter en rörelse, efter att ringa join , etc.).

Trådoperationer

När du startar en tråd kommer den att köras tills den är klar.

Ofta, vid någon tidpunkt, måste du (möjligen - tråden kanske redan är klar) vänta på att tråden är klar, för du vill använda resultatet till exempel.

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

Du kan också detach tråden och låta den köras fritt:

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

Skickar en referens till en tråd

Du kan inte skicka en referens (eller const referens) direkt till en tråd eftersom std::thread kommer att kopiera / flytta dem. Använd istället 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

Skapa en std :: tråd

I C ++ skapas trådar med klassen std :: tråd. En tråd är ett separat genomförandeflöde; det är analogt med att hjälpa att utföra en uppgift medan du samtidigt utför en annan. När all kod i tråden körs upphör den . När du skapar en tråd måste du skicka något för att köras på den. Några saker som du kan skicka till en tråd:

  • Gratis funktioner
  • Medlemsfunktioner
  • Functor objekt
  • Lambda-uttryck

Exempel på fri funktion - kör en funktion på en separat tråd ( Live-exempel ):

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

Exempel på medlemsfunktion - kör en medlemsfunktion i en separat tråd ( Live-exempel ):

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

Exempel på Functor-objekt ( Live-exempel ):

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

Exempel på Lambda-uttryck ( Live-exempel ):

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

Funktioner på den aktuella tråden

std::this_thread är ett namespace som har funktioner för att göra intressanta saker på den aktuella tråden från funktion den kallas från.

Fungera Beskrivning
get_id Returnerar trådens ID
sleep_for Sover under en viss tid
sleep_until Sov till en viss tid
yield Schemalägg löpa trådar och ge andra trådar prioritet

Hämta nuvarande tråd-id med 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

Sov i 3 sekunder med 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";

Sov fram till 3 timmar i framtiden med 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";

Att låta andra trådar prioritera med 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();

Använda std :: async istället för std :: tråd

std::async också skapa trådar. Jämfört med std::thread anses det vara mindre kraftfullt men lättare att använda när du bara vill köra en funktion asynkront.

Asynkront kallar en funktion

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

Vanliga fallgropar

  • std::async returnerar en std::future som håller tillbaka det värde som kommer att beräknas av funktionen. När den future blir förstörd väntar den tills tråden är klar, vilket gör din kod effektivt enkeltrådad. Detta förbises lätt när du inte behöver returvärdet:

    std::async(std::launch::async, square, 5);
    //thread already completed at this point, because the returning future got destroyed
    
  • std::async fungerar utan en lanseringspolicy, så std::async(square, 5); sammanställer. När du gör det får systemet besluta om det vill skapa en tråd eller inte. Tanken var att systemet väljer att skapa en tråd om det inte redan kör fler trådar än det kan köra effektivt. Tyvärr väljer implementeringar vanligtvis bara att inte skapa en tråd i den situationen någonsin, så du måste åsidosätta det beteende med std::launch::async som tvingar systemet att skapa en tråd.

  • Se upp för rasförhållanden.

Mer om async om Futures and Promises

Se till att en tråd alltid är förenad

När destruktorn för std::thread detach() måste ett samtal att antingen join() eller detach() ha ringts. Om en tråd inte har anslutits eller lossats kommer std::terminate att std::terminate standard. Med RAII är detta i allmänhet enkelt nog för att åstadkomma:

class thread_joiner
{
public:

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

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

private:

    std::thread t_;
}

Detta används sedan så:

 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

Detta ger också undantagssäkerhet; om vi hade skapat vår tråd normalt och arbetet som utförts i t() utföra andra beräkningar hade kastat ett undantag, skulle join() aldrig ha kallats på vår tråd och vår process skulle ha avslutats.

Tilldela trådobjekt på nytt

Vi kan skapa tomma trådobjekt och tilldela arbete till dem senare.

Om vi tilldelar ett trådobjekt till en annan aktiv, joinable tråd kommer std::terminate ringas automatiskt innan tråden byts ut.

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

Grundläggande synkronisering

Trådsynkronisering kan åstadkommas med hjälp av mutexer, bland andra primiser för synkronisering. Det finns flera mutextyper som tillhandahålls av standardbiblioteket, men det enklaste är std::mutex . För att låsa en mutex, konstruerar du ett lås på det. Den enklaste låstypen är 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

Med std::lock_guard är mutex låst under std::lock_guard hela livstid. I fall där du måste manuellt styra regionerna för låsning, använd istället 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
}

Mer trådsynkroniseringsstrukturer

Använda tillståndsvariabler

En tillståndsvariabel är en primitiv som används i samband med en mutex för att orkestrera kommunikation mellan trådar. Även om det varken är det exklusiva eller mest effektiva sättet att uppnå detta, kan det vara bland de enklaste för dem som är bekanta med mönstret.

Man väntar på en std::condition_variable med en std::unique_lock<std::mutex> . Detta gör att koden säkert kan undersöka delat tillstånd innan man bestämmer sig för att fortsätta med förvärvet eller inte.

Nedan finns en producent-konsumentskiss som använder std::thread , std::condition_variable , std::mutex och några andra för att göra saker intressanta.

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

Skapa en enkel trådpool

C ++ 11-trådprimitiv är fortfarande relativt låg nivå. De kan användas för att skriva en konstruktion på högre nivå, som en trådpool:

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; } ) returnerar en std::future<std::string> , som när uppgiften objektet kommer till att köra det är befolkat med hello world .

Du skapar trådar genom att köra tasks.start(10) (som startar 10 trådar).

Användningen av packaged_task<void()> beror bara på att det inte finns någon typ-raderad std::function som lagrar endast rörliga typer. Att skriva en anpassad en av dessa skulle förmodligen vara snabbare än att använda packaged_task<void()> .

Levande exempel .

C ++ 11

I C ++ 11, ersätt result_of_t<blah> med typename result_of<blah>::type .

Mer om Mutexes .

Tråd-lokal lagring

Trådlokal lagring kan skapas med hjälp av nyckelordet thread_local . En variabel som deklareras med thread_local specifikatorn sägs ha trådlagringsvaraktighet.

  • Varje tråd i ett program har sin egen kopia av varje tråd-lokal variabel.
  • En tråd-lokal variabel med funktion (lokal) omfattning kommer att initialiseras första gången kontrollen passerar sin definition. En sådan variabel är implicit statisk, såvida den inte förklaras extern .
  • En tråd-lokal variabel med namnutrymme eller klass (icke-lokalt) omfattning kommer att initialiseras som en del av trådstart.
  • Tråd-lokala variabler förstörs vid trådavslutning.
  • En gruppmedlem kan bara vara tråd-lokal om den är statisk. Det kommer därför att finnas en kopia av den variabeln per tråd snarare än en kopia per (tråd, instans) -par.

Exempel:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow