Ricerca…
Sintassi
- filo()
- thread (thread e altro)
- thread esplicito (Function && func, Args && ... args)
Parametri
Parametro | Dettagli |
---|---|
other | Prende la proprietà di other , other non possiedono più il thread |
func | Funzione per chiamare un thread separato |
args | Argomenti per func |
Osservazioni
Alcune note:
- Due oggetti
std::thread
non possono mai rappresentare lo stesso thread. - Un oggetto
std::thread
può trovarsi in uno stato in cui non rappresenta alcun thread (ovvero dopo uno spostamento, dopo aver chiamatojoin
, ecc.).
Operazioni di thread
Quando si avvia un thread, verrà eseguito fino al completamento.
Spesso, ad un certo punto, è necessario (probabilmente - il thread potrebbe già essere fatto) attendere il termine del thread, perché si desidera utilizzare il risultato, ad esempio.
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';
Puoi anche detach
il thread, lasciandolo eseguire liberamente:
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
Passando un riferimento a un thread
Non è possibile passare un riferimento (o riferimento const
) direttamente a un thread perché std::thread
lo copia / sposta. Invece, usa 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
Creare uno std :: thread
In C ++, i thread vengono creati utilizzando la classe std :: thread. Una discussione è un flusso separato di esecuzione; è analogo al fatto che un aiutante esegua un compito mentre contemporaneamente ne esegue un altro. Quando viene eseguito tutto il codice nel thread, termina . Quando si crea un thread, è necessario passare qualcosa da eseguire su di esso. Alcune cose che puoi passare ad un thread:
- Funzioni libere
- Funzioni membro
- Oggetti Functor
- Espressioni Lambda
Esempio di funzione libera - esegue una funzione su un thread separato ( esempio dal vivo ):
#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;
}
Esempio di funzione membro - esegue una funzione membro su un thread separato ( esempio dal vivo ):
#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;
}
Esempio di oggetto Functor (esempio dal vivo ):
#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;
}
Esempio di espressione Lambda (esempio dal vivo ):
#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;
}
Operazioni sul thread corrente
std::this_thread
è uno namespace
che ha funzioni per fare cose interessanti sul thread corrente dalla funzione da cui è chiamato.
Funzione | Descrizione |
---|---|
get_id | Restituisce l'id del thread |
sleep_for | Dorme per un determinato periodo di tempo |
sleep_until | Dorme fino a un'ora specifica |
yield | Ripianifica i thread in esecuzione, dando priorità agli altri thread |
Ottenere l'id corrente dei thread usando 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
Dormire per 3 secondi usando 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";
Dormire fino a 3 ore in futuro usando 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";
Lasciando che altri thread std::this_thread::yield
priorità usando 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();
Usando std :: async invece di std :: thread
std::async
è anche in grado di creare thread. Rispetto a std::thread
è considerato meno potente ma più facile da usare quando si desidera eseguire una funzione in modo asincrono.
Chiamata in modo asincrono di una funzione
#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
}
Insidie comuni
std::async
restituisce unostd::future
che contiene il valore restituito che verrà calcolato dalla funzione. Quando quelfuture
viene distrutto, attende il completamento del thread, rendendo il tuo codice effettivamente thread singolo. Questo è facilmente trascurato quando non hai bisogno del valore di ritorno:std::async(std::launch::async, square, 5); //thread already completed at this point, because the returning future got destroyed
std::async
funziona senza una politica di avvio, quindistd::async(square, 5);
compila. Quando lo fai, il sistema decide se vuole creare o meno una discussione. L'idea era che il sistema scelga di creare un thread a meno che non stia già eseguendo più thread di quanto possa funzionare in modo efficiente. Sfortunatamente le implementazioni comunemente scelgono di non creare un thread in quella situazione, mai, quindi è necessario sovrascrivere quel comportamento constd::launch::async
che forza il sistema a creare un thread.Attenzione alle condizioni di gara.
Altro su async su Futures and Promises
Assicurandosi che un thread sia sempre unito
Quando viene chiamato il distruttore per std::thread
, deve essere stata effettuata una chiamata a join()
o a detach()
. Se un thread non è stato unito o rimosso, per default verrà chiamato std::terminate
. Usando RAII , questo è generalmente abbastanza semplice da realizzare:
class thread_joiner
{
public:
thread_joiner(std::thread t)
: t_(std::move(t))
{ }
~thread_joiner()
{
if(t_.joinable()) {
t_.join();
}
}
private:
std::thread t_;
}
Questo viene quindi utilizzato in questo modo:
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
Questo fornisce anche la sicurezza delle eccezioni; se avessimo creato normalmente il nostro thread e il lavoro svolto in t()
eseguendo altri calcoli avesse generato un'eccezione, join()
non sarebbe mai stato chiamato sul nostro thread e il nostro processo sarebbe stato terminato.
Riassegnazione degli oggetti thread
Possiamo creare oggetti thread vuoti e assegnare loro il lavoro in un secondo momento.
Se assegniamo un oggetto thread a un altro thread joinable
attivo, std::terminate
verrà chiamato automaticamente prima che il thread venga sostituito.
#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);
}
Sincronizzazione di base
La sincronizzazione dei thread può essere eseguita utilizzando i mutex, tra le altre primitive di sincronizzazione. Esistono diversi tipi di mutex forniti dalla libreria standard, ma il più semplice è std::mutex
. Per bloccare un mutex, costruisci un blocco su di esso. Il tipo di blocco più semplice è 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
Con std::lock_guard
il mutex è bloccato per l'intera vita dell'oggetto lock. Nei casi in cui è necessario controllare manualmente le regioni per il blocco, utilizzare invece 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
}
Altre strutture di sincronizzazione dei thread
Uso delle variabili di condizione
Una variabile di condizione è una primitiva usata in congiunzione con un mutex per orchestrare la comunicazione tra i thread. Anche se non è né il modo esclusivo né il modo più efficace per farlo, può essere tra i più semplici per coloro che hanno familiarità con il modello.
Si attende una std::unique_lock<std::mutex>
std::condition_variable
con std::unique_lock<std::mutex>
. Ciò consente al codice di esaminare in sicurezza lo stato condiviso prima di decidere se procedere o meno con l'acquisizione.
Di seguito è riportato uno schizzo produttore-consumatore che utilizza std::thread
, std::condition_variable
, std::mutex
e pochi altri per rendere le cose interessanti.
#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;
}
Creare un pool di thread semplice
I primitivi di filettatura del C ++ 11 sono ancora relativamente bassi. Possono essere usati per scrivere un costrutto di livello superiore, come un pool di thread:
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; } )
restituisce uno std::future<std::string>
, che quando l'oggetto delle attività ottiene l'esecuzione, viene popolato con hello world
.
Puoi creare thread eseguendo tasks.start(10)
(che avvia 10 thread).
L'uso di packaged_task<void()>
è semplicemente perché non esiste un equivalente std::function
cancellato dal tipo che memorizza i tipi di spostamento. Scrivendo uno di quelli personalizzati sarebbe probabilmente più veloce di usare packaged_task<void()>
.
In C ++ 11, sostituire result_of_t<blah>
con typename result_of<blah>::type
.
Altro su Mutex .
Memorizzazione locale del thread
L'archiviazione locale del thread può essere creata usando la parola chiave thread_local
. Si dice che una variabile dichiarata con l' thread_local
abbia durata di memorizzazione del thread.
- Ogni thread in un programma ha la propria copia di ogni variabile locale del thread.
- Una variabile locale del thread con ambito di funzione (locale) verrà inizializzata al primo passaggio del controllo attraverso la sua definizione. Tale variabile è implicitamente statica, a meno che non sia dichiarata
extern
. - Una variabile locale del thread con spazio dei nomi o classe (non locale) verrà inizializzata come parte dell'avvio del thread.
- Le variabili locali del thread vengono distrutte al termine della terminazione del thread.
- Un membro di una classe può essere thread-local solo se è statico. Ci sarà quindi una copia di quella variabile per thread, piuttosto che una copia per coppia (thread, istanza).
Esempio:
void debug_counter() {
thread_local int count = 0;
Logger::log("This function has been called %d times by this thread", ++count);
}