C++ Thread

Da Andreabont's Wiki.
Versione del 1 gen 2021 alle 13:24 di Andreabont (Discussione | contributi) (Packaged task)

std::thread fornisce un accesso a basso livello alla gestione dei thread.

Compilazione

g++ <file.cpp> -std=c++11 -pthread

Codice

#include <thread>
#include <iostream>

void func(int x) {
   std::cout << x << std::endl;
}

int main() {

   std::thread t1(func, 2);
   t1.join();  // Attende la fine del thread

   std::thread t2(func, 3);
   t2.detach() // Lascia la gestione del thread e prosegue l'esecuzione

}

Opzioni avanzate

Thread local

Questa opzione, da specificare nella dichiarazione delle variabili globali, permette di specificare che quella variabile è da considerare locale per ogni thread (verrà copiata alla creazione del thread e rimarrà indipendente da quella usata dagli altri.)

thread_local int tls_var;

Ottenere numero di CPU presenti

std::thread::hardware_concurrency()

Ottenere id CPU in utilizzo

sched_getcpu()

Assegnare un thread ad una CPU

Sfruttando pthread è possibile 'suggerire' al sistema operativo di eseguire un thread solo su una CPU specificata.

#include <iostream>
#include <thread>
#include <pthread.h>

int main () {

   int cpu_num = 1;

   std::thread myThread([]{
      std::this_thread::sleep_for(std::chrono::milliseconds(20));
      std::cout << "Thread on CPU " << sched_getcpu() << std::endl;
   });

   cpu_set_t cpuset;
   CPU_ZERO(&cpuset);
   CPU_SET(cpu_num, &cpuset);
   int rc = pthread_setaffinity_np(myThread.native_handle(), sizeof(cpu_set_t), &cpuset);

   myThread.join();

}

Forzare la rischedulazione su altri thread

Tramite il metodo yeld è possibile chiedere al sistema operativo di togliere dall'esecuzione il thread in corso e eseguire gli altri in attesa. Questo può essere utile per migliorare le prestazioni in determinati casi, ad esempio:

#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>

using namespace std::literals::chrono_literals;

std::atomic<bool> flag {false};

void thread_code() {
    
    int count = 0;
    
    while(!flag) {
        std::this_thread::yield();
        count++;
    }
    
    std::cout << "Count: " << count << std::endl;
    
}

int main() {
    
    std::thread t(thread_code);
    
    std::this_thread::sleep_for(10s);
    
    flag = true;
    
    t.join();
    
}

Wrapper

Future

Un oggetto std::future fornisce il servizio di output in un contesto asincrono, attendendo l'arrivo di un valore.

std::future<int> myFuture; // Da specificare il tipo del valore atteso.
myFuture.get(); // Ottiene il risultato

Promise

Un oggetto std::promise fornisce il servizio di input in un contesto asincrono, promettendo un valore non necessariamente ancora disponibile.

std::promise<int> myPromise; // Da specificare il tipo valore che forniremo.
myPromise.set_value(10); // Fornire il valore

Packaged task

E' possibile eseguire un wrap di qualsiasi elemento chiamabile attraverso il template packaged_task, che funziona come il template function, con la differenza che permette la chiamata asincrona del contenuto.

int myFunction() {
    return 42;
}

std::packaged_task<int()> myTask(myFunction); // Wrapper della funzione

Codice di esempio

Future e Promise

#include <iostream>
#include <functional>
#include <thread>
#include <future>

void futureMul(std::future<int>& futureA, std::future<int>& futureB) {
    std::cout << "Result: " << futureA.get() * futureB.get() << '\n';
}

int main() {
   
    // Creo promise per i due parametri (input)
    std::promise<int> promiseA;
    std::promise<int> promiseB;
   
    // Ottengo i relativi future (output)
    std::future<int> futureA = promiseA.get_future();
    std::future<int> futureB = promiseB.get_future();
    
    // Lancio il thread con il codice e i future
    std::cout << "Inizio thread" << std::endl;
    std::thread threadMul(futureMul, std::ref(futureA), std::ref(futureB));
    
    // Fornisco i valori
    std::cout << "Setto valore A" << std::endl;
    promiseA.set_value(10);
    std::cout << "Setto valore B" << std::endl;
    promiseB.set_value(2);
    
    // Attendo fine thread
    threadMul.join();
    std::cout << "Fine thread" << std::endl;
    
}

Packaged task

#include <iostream>
#include <iostream>
#include <future>
#include <thread>
 
int main() {
    
    // Ottengo il task da una lambda
    std::packaged_task<int()> myTask([](){
        return 42;
    });
    
    // Ottengo il future dal task
    std::future<int> myFuture = myTask.get_future();
    
    // Passo esecuzione ad un thread
    std::thread myThread(std::move(myTask));
    
    // Attendo risultato
    std::cout << "Result: " << myFuture.get() << std::endl;
    
    // Attendo fine thread
    myThread.join();
    
}