Back to TIL list

Thread em C++

Created at

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çã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:

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

Referências