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 vanjoin
, 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 eenstd::future
met de retourwaarde die wordt berekend door de functie. Wanneer diefuture
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, dusstd::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 metstd::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:
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()>
.
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);
}