C++ Threads

Até aqui, todo programa fez uma coisa de cada vez: uma linha, depois a próxima. Mas o computador da Ana tem vários núcleos de processador, e muitos programas fazem várias coisas ao mesmo tempo: um jogo desenha a tela enquanto toca a música; um navegador baixa várias imagens de uma vez. Cada uma dessas "linhas de trabalho" é uma thread. Nesta aula você vai criar threads, ver os erros clássicos acontecerem de verdade (inclusive um que muda de resultado a cada execução) e aprender a primeira ferramenta para evitá-los, o std::mutex.

Você vai aprender
  • O que é concorrência e o que é uma thread.
  • Criar uma thread com std::thread e esperar por ela com join().
  • O que acontece quando você esquece o join() (terminate, o erro real).
  • Passar argumentos para a thread, e por que referências precisam de std::ref.
  • O que é uma condição de corrida, com um contador que dá um resultado diferente a cada execução.
  • Proteger dados com std::mutex e std::lock_guard.
  • std::jthread C++20, que espera sozinho, e o std::async, só de passagem.
Threads não rodam no editor do guia

O editor "Experimente" roda o seu programa dentro do navegador, num ambiente (WebAssembly) que não oferece threads. Um programa com std::thread até compila no editor, mas, ao rodar, termina assim (é a mensagem real do editor, que usa a biblioteca libc++ do Clang):

libc++abi: terminating due to uncaught exception of type std::__2::system_error: thread constructor failed: Not supported

("O construtor da thread falhou: não suportado".) Por isso, os programas com std::thread desta aula aparecem só no terminal do laboratório, com as saídas reais. As caixas de "Experimente" ensinam as partes que funcionam sem threads: a lógica de uma condição de corrida, simulada passo a passo, e a divisão de um trabalho em partes. Para rodar as threads de verdade, use o g++ no seu computador (aula Instalar no Linux).

Os programas foram compilados com o g++ 15.2 na máquina fictícia da Ana, na pasta ~/cpp, com o comando padrão g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. Todos usam no máximo duas threads além da principal e terminam em menos de um segundo. Você vai precisar de Funções, Por Valor × Por Referência e Lambdas.


A ideia: duas pessoas na mesma cozinha

Hoje é dia de almoço especial na casa da Ana. Se ela fizer tudo sozinha, primeiro cozinha, depois lava a louça. Se o Bruno ajudar, os dois trabalham ao mesmo tempo: ela cozinha enquanto ele lava. Isso é concorrência: vários trabalhos andando juntos.

Mas trabalhar junto traz problemas novos:

  • A Ana precisa esperar o Bruno terminar antes de servir o almoço (isso vai ser o join).
  • Se os dois forem marcar no mesmo caderninho quantos pratos já ficaram prontos, um pode apagar a marca do outro (isso vai ser a condição de corrida).
  • A solução para o caderninho: uma regra de "um de cada vez", como uma chave que só um pode segurar (isso vai ser o mutex).
PalavraQuer dizer
Thread ("fio", "linha")Uma linha de execução: uma sequência de instruções rodando. Todo programa começa com uma, a do main.
ConcorrênciaVários trabalhos em andamento ao mesmo tempo.
ParalelismoVários trabalhos rodando literalmente no mesmo instante, em núcleos diferentes do processador.

1. A primeira thread: std::thread e join

#include <iostream>
#include <thread>

void lavar_louca() {
    std::cout << "Bruno: lavando a louça\n";
}

int main() {
    std::thread ajudante(lavar_louca);
    std::cout << "Ana: fazendo o almoço\n";
    ajudante.join();
    std::cout << "Ana: tudo pronto!\n";
}
$ g++ -std=c++23 -Wall -Wextra primeira.cpp -o primeira
$ ./primeira
Ana: fazendo o almoço
Bruno: lavando a louça
Ana: tudo pronto!
#include <thread>
O cabeçalho das threads.
std::thread ajudante(lavar_louca);
Cria uma thread chamada ajudante, que começa imediatamente a executar a função lavar_louca. Repare: o nome da função vai sem parênteses. Você não está chamando a função; está entregando-a para a thread chamar.
std::cout << "Ana: …"
Enquanto isso, o main continua na thread principal.
ajudante.join();
Join = "juntar-se". O main para e espera até a thread ajudante terminar. Depois do join, é garantido que a louça foi lavada.

Quem escreveu primeiro, a Ana ou o Bruno? Não dá para saber de antemão. Aqui a Ana venceu, porque criar uma thread leva um tempinho; em outro computador, ou num momento em que o sistema estiver ocupado, a ordem pode se inverter. Em programas com threads, a ordem entre threads diferentes não é garantida, a menos que você mesmo a garanta (com join, por exemplo). O "tudo pronto!", esse sim, sempre sai por último: ele vem depois do join.

Preciso de -pthread?

Em tutoriais antigos, o comando para compilar threads no Linux tem -pthread no fim. Conferimos: com o g++ 15 do Ubuntu 26.04, o comando padrão do guia funciona sem ela, porque a biblioteca de threads passou a fazer parte da biblioteca C principal (glibc 2.34, de 2021). Em sistemas mais antigos, sem -pthread o programa pode falhar ao compilar ou ao rodar. Acrescentá-la não faz mal: se for usar um Linux antigo, escreva g++ -std=c++23 -Wall -Wextra -pthread ….

Esquecer o join: terminate

Toda std::thread criada precisa de um join() antes de ser destruída (ou de um detach(), que a "solta" para sempre e quase nunca é uma boa ideia). Se o main chegar ao fim com a thread ainda "presa" ao objeto, o C++ derruba o programa inteiro:

#include <iostream>
#include <thread>

void lavar_louca() {
    std::cout << "Bruno: lavando a louça\n";
}

int main() {
    std::thread ajudante(lavar_louca);
    std::cout << "Ana: fazendo o almoço\n";
}
$ g++ -std=c++23 -Wall -Wextra sem_join.cpp -o sem_join      « compila sem aviso nenhum
$ ./sem_join
Ana: fazendo o almoço
terminate called without an active exception
Abortado                   (imagem do núcleo gravada) ./sem_join
$ echo $?
134
terminate called without an active exception
"terminate foi chamado sem nenhuma exceção ativa." std::terminate é a função que encerra o programa à força. Normalmente ela aparece por causa de uma exceção não capturada (aula Exceções); aqui, não houve exceção: foi o objeto ajudante sendo destruído no fim do main sem ter feito join.
Abortado
O programa morreu com o sinal de abortar (código de saída 134 = 128 + 6).

E o Bruno nem chegou a lavar a louça: o programa morreu antes. A regra: para cada std::thread, um join(). Ou use a std::jthread da seção 5, que faz o join sozinha.


2. Passar argumentos

Depois do nome da função, coloque os argumentos que ela recebe, separados por vírgula:

#include <chrono>
#include <iostream>
#include <string>
#include <thread>

void baixar(std::string arquivo, int partes) {
    for (int i = 1; i <= partes; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::cout << arquivo + ": parte " + std::to_string(i) + " de " + std::to_string(partes) + "\n";
    }
}

int main() {
    std::thread t1(baixar, "fotos.zip", 3);
    std::thread t2(baixar, "musica.mp3", 2);

    t1.join();
    t2.join();
    std::cout << "Downloads concluídos\n";
}
$ g++ -std=c++23 -Wall -Wextra argumentos.cpp -o argumentos
$ ./argumentos
fotos.zip: parte 1 de 3
musica.mp3: parte 1 de 2             « os dois "downloads" andam juntos
fotos.zip: parte 2 de 3
musica.mp3: parte 2 de 2
fotos.zip: parte 3 de 3
Downloads concluídos

Os dois "downloads" (fingidos, com o sleep_for da aula Tempo e Datas) andaram lado a lado: o programa todo levou cerca de 0,3 s, e não 0,5 s, como levaria fazendo um depois do outro. A ordem entre as linhas de fotos.zip e musica.mp3 pode variar de uma execução para outra. Repare também que cada linha é montada inteira com + antes de ir para o cout: com vários << separados, os pedaços das duas threads poderiam se misturar no meio da linha.

Referências precisam de std::ref

A std::thread copia todos os argumentos, por segurança: a thread pode viver mais que as variáveis de quem a criou. Se a função recebe uma referência (para devolver um resultado, por exemplo), você precisa dizer explicitamente "passe por referência" com std::ref, de <functional>:

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

void contar_ate(int limite, int& resultado) {
    int soma = 0;
    for (int i = 1; i <= limite; ++i) {
        soma += i;
    }
    resultado = soma;
}

int main() {
    int total = 0;
    std::thread t(contar_ate, 100, std::ref(total));
    t.join();
    std::cout << "Soma de 1 a 100: " << total << '\n';
}
$ g++ -std=c++23 -Wall -Wextra referencia.cpp -o referencia
$ ./referencia
Soma de 1 a 100: 5050

Sem o std::ref (escrevendo só total), o g++ recusa:

$ g++ -std=c++23 -Wall -Wextra sem_ref.cpp -o sem_ref
In file included from /usr/include/c++/15/stop_token:40,
                 from /usr/include/c++/15/thread:44,
                 from sem_ref.cpp:3:
/usr/include/c++/15/bits/std_thread.h: In instantiation of ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(int, int&); _Args = {int, int&}; <template-parameter-1-3> = void]’:
sem_ref.cpp:15:41:   required from here
   15 |     std::thread t(contar_ate, 100, total);
      |                                         ^
/usr/include/c++/15/bits/std_thread.h:168:72: error: static assertion failed: std::thread arguments must be invocable after conversion to rvalues
  168 |                                       typename decay<_Args>::type...>::value,
      |                                                                        ^~~~~
                                     « … e mais 19 linhas

A frase importante é std::thread arguments must be invocable after conversion to rvalues: "os argumentos da std::thread precisam funcionar depois de convertidos em valores temporários". Traduzindo: a thread copiou o total para uma cópia temporária, e uma referência int& não pode se ligar a uma cópia temporária. Isso é proposital: se deixasse, a função alteraria a cópia, e o seu total continuaria 0 sem você entender por quê. A linha sem_ref.cpp:15:41: required from here aponta a sua linha.

Quem passa referência promete manter a variável viva

Com std::ref(total), a thread mexe na variável total do main. Ela precisa existir até a thread terminar, ou seja, até o join. É por isso que o join vem antes de usar o total.

Em vez de uma função separada, também dá para passar uma lambda: std::thread t([&total] { total = 42; });. A captura [&total] faz o papel do std::ref.


3. Condição de corrida: o contador que perde contas

Duas threads vão contar visitas num contador compartilhado, 100 mil cada uma. O resultado deveria ser 200 mil:

#include <iostream>
#include <thread>

int contador = 0;

void visitar() {
    for (int i = 0; i < 100000; ++i) {
        ++contador;
    }
}

int main() {
    std::thread a(visitar);
    std::thread b(visitar);
    a.join();
    b.join();
    std::cout << "Esperado: 200000, obtido: " << contador << '\n';
}
$ g++ -std=c++23 -Wall -Wextra corrida.cpp -o corrida
$ ./corrida
Esperado: 200000, obtido: 102492
$ ./corrida
Esperado: 200000, obtido: 111838
$ ./corrida
Esperado: 200000, obtido: 105624     « três execuções, três resultados, todos errados

Quase metade das visitas sumiu, e cada execução perdeu uma quantidade diferente. No seu computador, os números serão outros (podem até, por sorte, sair certos de vez em quando, o que torna esse erro ainda mais traiçoeiro). Esse defeito se chama condição de corrida (race condition): o resultado depende de quem "corre" mais rápido.

Por que as contas se perdem

++contador parece uma coisa só, mas para o processador são três passos: ler o valor da memória, somar 1, escrever de volta. Se as duas threads intercalarem esses passos, uma escrita apaga a outra:

PassoThread AThread Bcontador na memória
1lê: 00
2lê: 00
3soma: 0 + 1 = 10
4soma: 0 + 1 = 10
5escreve 11
6escreve 11 (deveria ser 2)

Duas visitas, uma só contada. Repetido milhares de vezes, sobra o resultado que você viu. Para o C++, duas threads mexendo na mesma variável ao mesmo tempo, com pelo menos uma escrevendo, é um data race ("corrida de dados") e é comportamento indefinido: perder contas é só um dos efeitos possíveis. O compilador não avisa nada. Para pegar esse erro ao rodar, existe o ThreadSanitizer, -fsanitize=thread (aula Sanitizers).

Você pode reproduzir a tabela sem threads, executando os passos na ordem errada de propósito. Isso funciona no editor:

Missão: o programa simula, sem threads, a tabela acima: Ana e Bruno leem o contador antes de o outro escrever, e uma soma se perde (sai Contador: 1). Reordene as linhas, sem mudar nenhuma, para que o Bruno só leia depois de a Ana escrever. A saída deve ser Contador: 2. É exatamente isso que o mutex da próxima seção garante.

#include <iostream>

int main() {
    int contador = 0;

    // Ana e Bruno querem somar 1 ao contador "ao mesmo tempo".
    int copia_ana = contador;        // 1. Ana lê o contador
    int copia_bruno = contador;      // 2. Bruno lê o contador
    copia_ana = copia_ana + 1;       // 3. Ana soma 1 na cópia dela
    copia_bruno = copia_bruno + 1;   // 4. Bruno soma 1 na cópia dele
    contador = copia_ana;            // 5. Ana escreve o resultado
    contador = copia_bruno;          // 6. Bruno escreve o resultado

    std::cout << "Contador: " << contador << '\n';
}
#include <iostream>

int main() {
    int contador = 0;

    // Ana e Bruno querem somar 1 ao contador "ao mesmo tempo".
    int copia_ana = contador;        // 1. Ana lê o contador
    copia_ana = copia_ana + 1;       // 3. Ana soma 1 na cópia dela
    contador = copia_ana;            // 5. Ana escreve o resultado
    int copia_bruno = contador;      // 2. Bruno lê o contador
    copia_bruno = copia_bruno + 1;   // 4. Bruno soma 1 na cópia dele
    contador = copia_bruno;          // 6. Bruno escreve o resultado

    std::cout << "Contador: " << contador << '\n';
}

4. std::mutex e std::lock_guard: um de cada vez

Um mutex (de mutual exclusion, "exclusão mútua") é como a chave do banheiro de um posto de gasolina: só quem está com a chave entra; os outros esperam na porta. Quem tranca o mutex antes de mexer no contador tem certeza de que ninguém mais está mexendo nele ao mesmo tempo.

#include <iostream>
#include <mutex>
#include <thread>

int contador = 0;
std::mutex trava;

void visitar() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> guarda(trava);
        ++contador;
    }
}

int main() {
    std::thread a(visitar);
    std::thread b(visitar);
    a.join();
    b.join();
    std::cout << "Esperado: 200000, obtido: " << contador << '\n';
}
$ g++ -std=c++23 -Wall -Wextra mutex.cpp -o mutex
$ ./mutex
Esperado: 200000, obtido: 200000
$ ./mutex
Esperado: 200000, obtido: 200000     « certo, toda vez
#include <mutex>
O cabeçalho do mutex e do lock_guard.
std::mutex trava;
O mutex, compartilhado pelas threads (como o contador). Ele começa destrancado.
std::lock_guard<std::mutex> guarda(trava);
Tranca o mutex. Se outra thread estiver com ele, esta espera aqui até ele ser liberado. Lock guard = "guarda da tranca".
++contador;
Agora os três passos (ler, somar, escrever) acontecem sem ninguém no meio.
} (fim da volta do for)
O guarda sai de escopo e destranca sozinho o mutex. É a técnica RAII (aula Destrutores e RAII).

Por que não chamar trava.lock() e trava.unlock() na mão? Funciona, mas se você esquecer o unlock (ou se uma exceção acontecer no meio), o mutex fica trancado para sempre e as outras threads esperam eternamente: o programa trava (um deadlock). O lock_guard não esquece.

Proteger custa

Com o mutex, as duas threads passam boa parte do tempo esperando uma pela outra. No laboratório, medindo com time, a versão com mutex levou cerca de 0,026 s, contra 0,003 s da versão sem proteção (que dá o resultado errado): quase dez vezes mais. O mutex garante o resultado certo; a velocidade vem de dividir o trabalho para que as threads mexam o mínimo possível em dados compartilhados, como na missão final. Para um simples contador, também existe o std::atomic<int>, de <atomic>, que faz o ++ inteiro de uma vez só, sem mutex.


5. std::jthread: a thread que espera sozinha C++20

O C++20 trouxe a std::jthread ("joining thread", "thread que se junta"). Ela é igual à std::thread, com uma diferença: quando o objeto é destruído, ela faz o join sozinha. Lembra do programa que morria por falta de join? Trocando só o tipo:

#include <iostream>
#include <thread>

void lavar_louca() {
    std::cout << "Bruno: lavando a louça\n";
}

int main() {
    std::jthread ajudante(lavar_louca);
    std::cout << "Ana: fazendo o almoço\n";
}
$ g++ -std=c++23 -Wall -Wextra jthread.cpp -o jthread
$ ./jthread
Ana: fazendo o almoço
Bruno: lavando a louça               « no fim do main, a jthread esperou o Bruno

Em código novo, prefira std::jthread: é o mesmo princípio do lock_guard, o objeto cuida da limpeza sozinho. Ela também sabe receber um pedido de "pare" (request_stop), útil para threads que ficam rodando num laço; isso fica para quando você precisar. Com -std=c++17, o g++ avisa: error: ‘jthread’ is not a member of ‘std’ e note: ‘std::jthread’ is only available from C++20 onwards.

std::async: só para conhecer

Muitas vezes o que você quer é "calcule isto em paralelo e me dê o resultado depois". O std::async, de <future>, faz isso sem você criar a thread na mão: ele devolve um std::future ("futuro"), uma promessa de resultado, e o .get() espera e entrega o valor:

#include <future>
#include <iostream>

long long somar(int de, int ate) {
    long long s = 0;
    for (int i = de; i <= ate; ++i) s += i;
    return s;
}

int main() {
    std::future<long long> metade1 = std::async(std::launch::async, somar, 1, 500);
    long long metade2 = somar(501, 1000);
    std::cout << "Total: " << metade1.get() + metade2 << '\n';
}
$ g++ -std=c++23 -Wall -Wextra assincrono.cpp -o assincrono
$ ./assincrono
Total: 500500

Enquanto uma thread soma de 1 a 500, o main soma de 501 a 1000. O std::launch::async pede uma thread nova de verdade. Os detalhes de future e async vão além deste guia; guarde que existem.


Dividir o trabalho

O uso mais comum de threads é este: um trabalho grande, dividido em partes independentes, cada parte numa thread, e no fim os resultados se juntam. Cada thread escreve na sua própria variável, então não há corrida e não é preciso mutex:

#include <iostream>
#include <thread>

void somar_intervalo(int inicio, int fim, long long& resultado) {
    long long soma = 0;
    for (int i = inicio; i <= fim; ++i) {
        soma += i;
    }
    resultado = soma;
}

int main() {
    long long parte1 = 0;
    long long parte2 = 0;

    std::thread t1(somar_intervalo, 1, 50, std::ref(parte1));
    std::thread t2(somar_intervalo, 51, 100, std::ref(parte2));
    t1.join();
    t2.join();

    std::cout << "Parte 1: " << parte1 << '\n';
    std::cout << "Parte 2: " << parte2 << '\n';
    std::cout << "Total: " << parte1 + parte2 << '\n';
}
$ g++ -std=c++23 -Wall -Wextra dividir.cpp -o dividir
$ ./dividir
Parte 1: 1275
Parte 2: 3775
Total: 5050

Para somar 100 números, threads não valem a pena: criar uma thread custa mais que essa conta. Elas compensam com trabalhos que levam segundos (processar milhares de fotos, por exemplo). E mais threads não é sempre melhor: além do número de núcleos do processador, elas só disputam o mesmo lugar. A missão final pratica a divisão das partes; no editor, sem threads, as duas chamadas rodam uma depois da outra, mas a lógica de "cada parte com sua variável" é a mesma.


Quando dá errado

terminate called without an active exception

O que significa: um objeto std::thread foi destruído (no fim do bloco em que foi criado) sem join() nem detach().

Como consertar: chame t.join() antes do fim do bloco, ou use std::jthread.

static assertion failed: std::thread arguments must be invocable after conversion to rvalues

O que significa: a função da thread recebe uma referência (int&), e você passou a variável direto. A thread copia os argumentos, e a cópia não serve para a referência.

Como consertar: passe std::ref(variavel) (com #include <functional>), ou use uma lambda que capture por referência.

O resultado muda a cada execução

O que significa: condição de corrida: duas threads mexem na mesma variável sem proteção. É comportamento indefinido, e o compilador não avisa.

Como consertar: proteja o acesso com std::mutex + std::lock_guard, use std::atomic para contadores simples, ou, melhor ainda, dê a cada thread sua própria variável e junte no fim. Para encontrar a corrida: -fsanitize=thread.

O programa trava e não termina

O que significa: provavelmente um deadlock: uma thread espera um mutex que nunca é liberado (um lock() sem unlock()), ou duas threads esperam uma pela outra.

Como consertar: aperte Ctrl+C para parar. Use sempre std::lock_guard em vez de lock/unlock na mão, e mantenha o trecho trancado o menor possível.


Referência rápida

EscritaO que faz
std::thread t(funcao, arg1, arg2);Cria uma thread que roda funcao(arg1, arg2) (<thread>)
t.join();Espera a thread terminar
t.detach();Solta a thread (evite)
std::ref(x)Passa x por referência para a thread (<functional>)
std::jthread t(funcao);Thread que faz join sozinha no fim (C++20)
std::mutex m;Uma tranca (<mutex>)
std::lock_guard<std::mutex> g(m);Tranca até o fim do bloco
std::atomic<int> c = 0;Contador seguro sem mutex (<atomic>)
std::async(std::launch::async, f, args…)Roda f em paralelo; resultado com .get() (<future>)
std::this_thread::sleep_for(100ms);Pausa a thread atual

Exercícios

1. Esperar

Complete para esperar a thread t terminar:

t.;

2. Por referência

Complete para a thread receber total por referência:

std::thread t(somar, std::(total));

3. O que acontece se o main terminar com uma std::thread sem join()?

4. Duas threads fazem ++contador 1000 vezes cada, sem mutex. O que se pode afirmar sobre o resultado?

5. Qual a vantagem do std::lock_guard sobre chamar lock() e unlock() na mão?

Missão final: dividir a soma em duas partes

Missão: a soma de 1 a 100 foi dividida em duas partes, como se cada uma fosse para uma thread. Só a primeira parte está sendo calculada. Chame somar_intervalo para a segunda parte (de 51 a 100, guardando em parte2). A saída deve terminar com Parte 2: 3775 e Total: 5050. (No laboratório, com std::thread, a versão com threads deu o mesmo resultado; veja a seção "Dividir o trabalho".)

#include <iostream>

void somar_intervalo(int inicio, int fim, long long& resultado) {
    long long soma = 0;
    for (int i = inicio; i <= fim; ++i) {
        soma += i;
    }
    resultado = soma;
}

int main() {
    long long parte1 = 0;
    long long parte2 = 0;

    somar_intervalo(1, 50, parte1);
    // calcule a parte 2 aqui

    std::cout << "Parte 1: " << parte1 << '\n';
    std::cout << "Parte 2: " << parte2 << '\n';
    std::cout << "Total: " << parte1 + parte2 << '\n';
}
#include <iostream>

void somar_intervalo(int inicio, int fim, long long& resultado) {
    long long soma = 0;
    for (int i = inicio; i <= fim; ++i) {
        soma += i;
    }
    resultado = soma;
}

int main() {
    long long parte1 = 0;
    long long parte2 = 0;

    somar_intervalo(1, 50, parte1);
    somar_intervalo(51, 100, parte2);

    std::cout << "Parte 1: " << parte1 << '\n';
    std::cout << "Parte 2: " << parte2 << '\n';
    std::cout << "Total: " << parte1 + parte2 << '\n';
}

Resumo

  • Uma thread é uma linha de execução. std::thread t(funcao, args…); cria e já começa a rodar.
  • Toda std::thread precisa de join(); sem ele, o programa morre com terminate called without an active exception. std::jthread (C++20) faz o join sozinha.
  • A ordem entre threads não é garantida. Só o join (e as travas) põem ordem.
  • Argumentos são copiados; para referência, std::ref(x).
  • Duas threads mexendo na mesma variável sem proteção = condição de corrida: resultados errados e diferentes a cada execução, sem aviso do compilador.
  • std::mutex + std::lock_guard: um de cada vez, destrancando sozinho no fim do bloco.
  • O melhor é dividir o trabalho para que cada thread tenha seus próprios dados, e juntar no fim.
  • No g++ 15 do Ubuntu 26.04, -pthread não é necessário; em sistemas antigos, acrescente.