Publicado em: 23/Jun/2020
Atualizado em: 13/Jul/2020
#C++ #cpp #thread

Thread em C++

Thread com função sem argumentos

1void foo() {
2  cout << "Corpo da função: " << this_thread::get_id() << endl;
3}
4
5void run () {
6  thread t1( foo );
7  // Algum processamento...
8  t1.join();
9}

Thread com função passando parâmetros por valor

 1void fooByRef( int & a ) {
 2  while( true ) {
 3    cout << "O valor é " << a << endl;
 4    this_thread::sleep_for( chrono::milliseconds( 1000 ) );
 5  }
 6}
 7
 8void run () {
 9  int p = 9;
10  thread t2( fooByRef, std::ref( p ) ); // usar o wrapper ref para não passar por valor
11  // Algum processamento...
12  this_thread::sleep_for( chrono::milliseconds( 5000 ) );
13  p = 30;
14  t1.join();
15}

Thread com função passando parâmetros por referência

 1void foo( int a, int b ) {
 2}
 3
 4void run () {
 5  int p = 9;
 6  int q = 8;
 7  thread t1( foo, p, q );
 8  // Algum processamento...
 9  t1.join();
10}

Thread com classe callable

 1class CallableClass {
 2  public:
 3    void operator()() {
 4      cout << "Corpo da função: " << this_thread::get_id() << endl;
 5    }
 6};
 7
 8void run() {
 9  CallableClass obj;
10  thread t2( obj );
11  // Algum processamento...
12  t2.join();
13}

Thread com lambda

 1// Sintaxe
 2// [capture-list]( params ) /*mutable*/ /*constexpr*/ /*exception attribute*/ -> ret { body }
 3// [capture-list]( params ) -> ret { body }
 4// [capture-list]( params ) { body }
 5// [capture-list]{ body }
 6
 7void run() {
 8  thread t3( []{
 9    cout << "Corpo da função: " << this_thread::get_id() << endl;
10  } );
11  // Algum processamento...
12  t3.join();
13}

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ção
    • detach - 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:

1void run() {
2  thread t4( foo );
3  throw runtime_error( "Algum erro antes do join/detach" );
4  t4.join(); // esta linha nunca vai ser executada
5}

A classe ThreadGuard fornece um auxiliar para garantir que o join será sempre executado quando o objeto sair do contexto.

 1class ThreadGuard {
 2  thread & mThread;
 3
 4  public:
 5    // garantir que não haverá conversões implícitas
 6    explicit ThreadGuard( thread & t ) : mThread( t ) {  }
 7
 8    // realiza o join pelo destrutor
 9    ~ThreadGuard() { if( mThread.joinable() ) { mThread.join(); } }
10
11    // não permite o uso dos contrutores de cópia e atribuição
12    ThreadGuard( const ThreadGuard & ) = delete;
13    ThreadGuard & operator= ( const ThreadGuard & ) = delete;
14};
15
16void run() {
17  thread t4( foo );
18  ThreadGuard tg( t4 ); // quando sair do escopo o join será chamado
19  throw runtime_error( "Este erro não afeta a thread acima" );
20}

Transferindo a posse das threads

 1void foo() { }
 2void bar() { }
 3
 4void run() {
 5  thread t1( foo );
 6  // thread t2 = t1; // Se fizer isso vai gerar erro de compilação
 7
 8  // usar move para transferir a posse da thread
 9  thread t2 = std::move( t1 ); // t1 não é mais dono da thread
10  t1 = thread( bar ); // t1 tem nova thread
11
12  t1.join();
13  t2.join();
14}

thread::get_id()

  • retorna um id único para cada thread ativa em execução
  • retorna 0 para todas as threads não ativas
 1void run() {
 2  thread t1( foo );
 3  thread t2( foo );
 4  thread t3;
 5
 6  cout << t1.get_id() << endl; // 1234
 7  cout << t2.get_id() << endl; // 5678
 8  cout << t3.get_id() << endl; // 0
 9
10  t1.join();
11  t2.join();
12
13  cout << t1.get_id() << endl; // 0
14  cout << t2.get_id() << endl; // 0
15}

sleep_for

std::this_thread::yield()

  • retorna a fatia de tempo
  • reinsere a thread na fila de execução

std:🧵: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

Referências

comments powered by Disqus