Thread em C++
Date: 2020-06-23Last modified: 2022-12-27

Thread com função sem argumentos
void foo() {
cout << "Corpo da função: " << this_thread::get_id() << endl;
}
void run () {
thread t1( foo );
// Algum processamento...
t1.join();
}
Thread com função passando parâmetros por valor
void fooByRef( int & a ) {
while( true ) {
cout << "O valor é " << a << endl;
this_thread::sleep_for( chrono::milliseconds( 1000 ) );
}
}
void run () {
int p = 9;
thread t2( fooByRef, std::ref( p ) ); // usar o wrapper ref para não passar por valor
// Algum processamento...
this_thread::sleep_for( chrono::milliseconds( 5000 ) );
p = 30;
t1.join();
}
Thread com função passando parâmetros por referência
void foo( int a, int b ) {
}
void run () {
int p = 9;
int q = 8;
thread t1( foo, p, q );
// Algum processamento...
t1.join();
}
Thread com classe callable
class CallableClass {
public:
void operator()() {
cout << "Corpo da função: " << this_thread::get_id() << endl;
}
};
void run() {
CallableClass obj;
thread t2( obj );
// Algum processamento...
t2.join();
}
Thread com lambda
// Sintaxe
// [capture-list]( params ) /*mutable*/ /*constexpr*/ /*exception attribute*/ -> ret { body }
// [capture-list]( params ) -> ret { body }
// [capture-list]( params ) { body }
// [capture-list]{ body }
void run() {
thread t3( []{
cout << "Corpo da função: " << this_thread::get_id() << endl;
} );
// Algum processamento...
t3.join();
}
Join, detach e terminate
- Uma thread representa um objeto de execução em nível de hardware e está num estado chamado joinable
- A opções no estado joinable são:
join
- introduz um ponto de sincronismo e bloqueia a execuçãodetach
- faz a execução continuar em modo independente
- Após esta escolha a thread se torna non joinable
- Se esquecer de realizar o join ou detach a função
std::terminate
será chamada pelo destrutor - Um programa com
std::terminate
é considerado não seguro.
Tratamento de exceção
Para evitar problemas caso uma exceção for lançada antes do join
use
RAII - Resource acquisition is initialization.
RAII:
- Construtor adquire os recursos
- Destrutor libera os recursos
Exemplo de código com problema:
void run() {
thread t4( foo );
throw runtime_error( "Algum erro antes do join/detach" );
t4.join(); // esta linha nunca vai ser executada
}
A classe ThreadGuard
fornece um auxiliar para garantir que o join
será
sempre executado quando o objeto sair do contexto.
class ThreadGuard {
thread & mThread;
public:
// garantir que não haverá conversões implícitas
explicit ThreadGuard( thread & t ) : mThread( t ) { }
// realiza o join pelo destrutor
~ThreadGuard() { if( mThread.joinable() ) { mThread.join(); } }
// não permite o uso dos contrutores de cópia e atribuição
ThreadGuard( const ThreadGuard & ) = delete;
ThreadGuard & operator= ( const ThreadGuard & ) = delete;
};
void run() {
thread t4( foo );
ThreadGuard tg( t4 ); // quando sair do escopo o join será chamado
throw runtime_error( "Este erro não afeta a thread acima" );
}
Transferindo a posse das threads
void foo() { }
void bar() { }
void run() {
thread t1( foo );
// thread t2 = t1; // Se fizer isso vai gerar erro de compilação
// usar move para transferir a posse da thread
thread t2 = std::move( t1 ); // t1 não é mais dono da thread
t1 = thread( bar ); // t1 tem nova thread
t1.join();
t2.join();
}
thread::get_id()
- retorna um id único para cada thread ativa em execução
- retorna 0 para todas as threads não ativas
void run() {
thread t1( foo );
thread t2( foo );
thread t3;
cout << t1.get_id() << endl; // 1234
cout << t2.get_id() << endl; // 5678
cout << t3.get_id() << endl; // 0
t1.join();
t2.join();
cout << t1.get_id() << endl; // 0
cout << t2.get_id() << endl; // 0
}
sleep_for
std::this_thread::yield()
- retorna a fatia de tempo
- reinsere a thread na fila de execução
std::thread::hardware_concurrency()
- retorna o número de thread concorrentes suportado pela implementação do sistema
- considere este valor apenas como uma dica
- é usualmente o número de cores lógicos