Back to TILs

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

Tratamento de exceção

Para evitar problemas caso uma exceção for lançada antes do join use RAII - Resource acquisition is initialization.

RAII:

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()

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()

std::thread::hardware_concurrency()

Referências