Crie um pool de threads em C ++

Crie um pool de threads em C ++
Um pool de threads é um conjunto de threads onde cada thread tem um tipo de tarefa para realizar. Portanto, tópicos diferentes realizam diferentes tipos de tarefas. Portanto, cada tópico tem sua especialização de tarefas. Uma tarefa é basicamente uma função. Funções semelhantes são feitas por um fio específico; Um conjunto semelhante de funções semelhantes é feito por outro tópico e assim por diante. Embora um thread de execução execute uma função de nível superior, um encadeamento por definição é a instanciação de um objeto da classe Thread. Diferentes tópicos têm argumentos diferentes; portanto, um fio específico deve atender a um conjunto semelhante de funções.

Em C ++, este pool de tópicos deve ser gerenciado. C ++ não possui uma biblioteca para criar um pool de threads e é gerenciamento. Provavelmente porque existem diferentes maneiras de criar um pool de threads. Portanto, um programador C ++ precisa criar um pool de threads com base nas necessidades.

O que é um tópico? Um tópico é um objeto instanciado da classe Thread. Em instanciação normal, o primeiro argumento do construtor de thread é o nome de uma função de nível superior. O restante dos argumentos para o construtor de threads são argumentos para a função. À medida que o tópico é instanciado, a função começa a executar. A função C ++ Main () é uma função de nível superior. Outras funções nesse escopo global são funções de nível superior. Acontece que a função principal () é um encadeamento que não precisa de declaração formal, como outros threads fazem. Considere o seguinte programa:

#incluir
#incluir
usando namespace std;
void func ()
cout << "code for first output" << endl;
cout << "code for second output" << endl;

int main ()

Thread Thr (func);
Thr.juntar();
/ * Outras declarações */
retornar 0;

A saída é:

código para primeira saída
código para segunda saída

Observe a inclusão da biblioteca de threads que tem a classe Thread. func () é uma função de nível superior. A primeira declaração na função Main () a usa na instanciação do thread, thr. A próxima declaração em Main () é uma declaração de junção. Ele se junta ao fio THR ao corpo do segmento de função principal (), na posição em que é codificado. Se esta declaração estiver ausente, a função principal poderá ser executada até a conclusão sem a conclusão da função de thread. Isso significa problemas.

Um comando semelhante ao seguinte, deve ser usado para executar um programa C ++ 20 de threads, para o compilador G ++:

g ++ -std = c ++ 2a temp.cpp -lpthread -o temp

Este artigo explica uma maneira de criar e gerenciar um pool de threads em C++.

Conteúdo do artigo

  • Requisitos de exemplo do pool de threads
  • Variáveis ​​globais
  • A função do tópico principal
  • função principal
  • Conclusão

Requisitos de exemplo do pool de threads

Os requisitos para este pool de threads ilustrativos são simples: existem três threads e um thread mestre. Os threads estão subordinados ao tópico mestre. Cada encadeamento subordinado funciona com uma estrutura de dados da fila. Portanto, existem três filas: qu1, qu2 e qu3. A biblioteca da fila, assim como a biblioteca de threads, deve ser incluída no programa.

Cada fila pode ter mais de uma chamada de função, mas da mesma função de nível superior. Ou seja, cada elemento de uma fila é para uma chamada de função de uma função específica de nível superior. Portanto, existem três funções diferentes de nível superior: uma função de nível superior por thread. Os nomes de funções são FN1, FN2 e FN3.

A função exige cada fila difere apenas em seus argumentos. Por simplicidade e para este exemplo do programa, as chamadas de função não terão argumento. De fato, o valor de cada fila neste exemplo será o mesmo número inteiro: 1 que o valor para todos os elementos Qu1; 2 como o valor para todos os elementos Qu2; e 3 como o valor para todos os elementos Qu3.

Uma fila é uma estrutura First_in-First_Out. Portanto, a primeira chamada (número) a entrar em uma fila é a primeira a sair. Quando uma chamada (número) sai, a função correspondente e seu fio são executados.

A função principal () é responsável por alimentar cada uma das três filas, com chamadas para as funções apropriadas, portanto, tópicos apropriados.

O tópico principal é responsável por verificar se houver uma chamada em qualquer fila e, se houver uma chamada, ele chama a função apropriada por meio de seu tópico. Neste exemplo de programa, quando nenhuma fila tem nenhum tópico, o programa termina.

As funções de nível superior são simples, para este exemplo pedagógico, elas são:

void fn1 ()
cout << "fn1" << endl;

void fn2 ()
cout << "fn2" << endl;

void fn3 ()
cout << "fn3" << endl;

Os threads correspondentes serão Thr1, Thr2 e Thr3. O tópico principal tem sua própria função mestre. Aqui, cada função tem apenas uma declaração. A saída da função fn1 () é "fn1". A saída da função fn2 () é "fn2". A saída da função fn3 () é "fn3".

No final deste artigo, o leitor pode montar todos os segmentos de código deste artigo para formar um programa de pool de threads.

Variáveis ​​globais

O topo do programa com as variáveis ​​globais é:

#incluir
#incluir
#incluir
usando namespace std;
fila qu1;
fila qu2;
fila qu3;
Thread Thr1;
Thread Thr2;
Thread Thr3;

As variáveis ​​de fila e rosca são variáveis ​​globais. Eles foram declarados sem inicialização ou declaração. Depois disso, no programa, deve ser as três funções subordinadas de nível superior, como mostrado acima.

A biblioteca iostream está incluída para o objeto Cout. A biblioteca de threads está incluída para os threads. Os nomes dos threads são Thr1, Thr2 e Thr3. A biblioteca da fila está incluída para as filas. Os nomes das filas são Qu1, Qu2 e Qu3. Qu1 corresponde a Thr1; Qu2 corresponde a Thr2 e Qu3 corresponde a Thr3. Uma fila é como um vetor, mas é para FIFO (FIRST_IN-FIRST_OUT).

A função do tópico principal

Após as três funções subordinadas de nível superior, são a função mestre no programa. Isso é:

void masterfn ()
trabalhar:
if (qu1.tamanho ()> 0) Thr1 = Thread (FN1);
if (qu2.tamanho ()> 0) Thr2 = Thread (FN2);
if (qu3.tamanho ()> 0) Thr3 = Thread (FN3);
if (qu1.tamanho ()> 0)
qu1.pop ();
Thr1.juntar();

if (qu2.tamanho ()> 0)
qu2.pop ();
Thr2.juntar();

if (qu3.tamanho ()> 0)
qu3.pop ();
Thr3.juntar();

if (qu1.tamanho () == 0 && qu1.tamanho () == 0 && qu1.tamanho () == 0)
retornar;
ir trabalhar;

O loop de goto incorpora todo o código da função. Quando todas as filas estão vazias, a função retorna vazia, com a declaração "Return;".

O primeiro segmento de código no loop de goto tem três declarações: uma para cada fila e o tópico correspondente. Aqui, se uma fila não estiver vazia, seu fio (e a função subordinada de nível superior correspondente) é executada.

O próximo segmento de código consiste em três construções IF, cada um correspondente a um thread subordinado. Cada consumo do IF tem duas declarações. A primeira declaração remove o número (para a chamada), que pode ter ocorrido no primeiro segmento de código. O próximo é uma declaração de junção, que garante que o tópico correspondente funcione para a conclusão.

A última declaração no loop de goto termina a função, saindo do loop se todas as filas estiverem vazias.

Função principal

Após a função de thread mestre no programa, deve ser a função principal (), cujo conteúdo é:

qu1.push (1);
qu1.push (1);
qu1.push (1);
qu2.push (2);
qu2.push (2);
qu3.push (3);
Thread Masterthr (MasterFN);
cout << "Program has started:" << endl;
Masterthr.juntar();
cout << "Program has ended." << endl;

A função principal () é responsável por colocar números que representam chamadas nas filas. Qu1 tem três valores de 1; qu2 tem dois valores de 2 e qu3 tem um valor de 3. A função principal () inicia o fio principal e se junta ao seu corpo. Uma saída do computador do autor é:

O programa começou:
fn2
fn3
fn1
fn1
fn2
fn1
O programa terminou.

A saída mostra as operações simultâneas irregulares dos threads. Antes que a função Main () se junte ao seu tópico principal, ele exibe "o programa começou:". O encadeamento mestre chama Thr1 para FN1 (), Thr2 para FN2 () e Thr3 para FN3 (), nessa ordem. No entanto, a saída correspondente começa com "FN2", depois "FN3" e depois "FN1". Não há nada de errado com este pedido inicial. É assim que a concorrência opera, irregularmente. O restante das seqüências de saída aparece como suas funções foram chamadas.

Depois que o corpo da função principal entrou no tópico mestre, ele esperou que o tópico principal concluísse. Para que o tópico mestre seja concluído, todas as filas precisam estar vazias. Cada valor da fila corresponde à execução de seu encadeamento correspondente. Portanto, para que cada fila fique vazia, seu fio deve executar para esse número de vezes; Existem elementos na fila.

Quando o tópico mestre e seus tópicos foram executados e terminados, a função principal continua a executar. E exibe: “O programa terminou.”.

Conclusão

Um pool de threads é um conjunto de threads. Cada tópico é responsável por realizar suas próprias tarefas. Tarefas são funções. Em teoria, as tarefas estão sempre chegando. Eles realmente não terminam, como ilustrado no exemplo acima. Em alguns exemplos práticos, os dados são compartilhados entre threads. Para compartilhar dados, o programador precisa de conhecimento de condicional_variável, função assíncrona, promessa e futuro. Essa é uma discussão para algum outro momento.