Linux | | Tópico

Fio

1. O conceito de fios

1.1 O que é um tópico

  • Thread é uma sequência de controle dentro do processo

  • Todos os processos devem ter pelo menos uma rota de execução

 

1.2 Processos e threads

  • Processo é a menor unidade de alocação de recursos

  • A menor unidade de execução de programa em um thread (agendamento)

  • Todas as informações do processo são compartilhadas pelo encadeamento, incluindo o texto do programa executável, a memória global e a pilha do programa, a memória heap e os descritores de arquivo, mas o encadeamento também tem alguns de seus próprios dados privados:

    • ID de thread, registro, pilha de execução, errno, palavra de máscara de sinal, prioridade de agendamento

por exemplo:

Por exemplo, a escola deseja realizar uma reunião esportiva e, em seguida, deve fazer várias coisas, compras, planejamento e assim por diante. Para fazer compras, o planejamento dessas coisas deve ser feito por pessoas diferentes ao mesmo tempo.

As escolas são como processos e as pessoas que fazem coisas diferentes são como fios.

【Nota】

  • Para o Linux, não há conceito de threads, todos são processos.

  • Assim, para a criação de um thread, um novo processo é criado e, em seguida, o PCB do processo e o PCB do processo anterior apontam para o mesmo espaço de endereço virtual. Dessa forma, os dados podem ser compartilhados e diferentes PCBs têm um desempenho diferente. Código.

  • O processo é chamado de processo leve

  • O tópico é criado com base no processo como modelo

 

1.3 Compartilhado por vários threads de um processo

Os threads estão todos no mesmo espaço de endereço, portanto, o Segmento de Texto e o Segmento de Dados são compartilhados.

  • Se você definir uma função, ela pode ser chamada em cada thread

  • Defina uma variável global, que pode ser acessada em todos os threads

  • Além disso, os tópicos também compartilham:

    • Tabela descritor de arquivo

    • Cada método de processamento de sinal

      • SIG_IGN (ignorado), SIG_DFL (padrão, geralmente termina o processo) ou função de processamento de sinal definida pelo usuário

    • Diretório de trabalho atual

    • ID de usuário e ID de grupo

 

1.4 Vantagens dos fios

  • O custo de criação de um novo thread é muito menor do que o custo de um processo

    • Porque a criação de um processo precisa abrir uma série de operações, como espaço de endereço virtual, tabela de página, PCB, etc. Para threads, recursos como espaço de endereço virtual e tabela de página já foram desenvolvidos e você só precisa desenvolver o PCB.

  • Em comparação com a alternância entre processos, alternar entre threads requer pouco trabalho do sistema operacional

    • Para alternar entre processos, você precisa alternar espaços de endereço virtual, tabelas de página, etc., mas para alternar thread, você só precisa alterar o PCB.

  • Threads ocupam recursos muito menores do que processos

    • A maioria dos recursos do thread são processos compartilhados e apenas uma pequena parte de seus próprios recursos privados. Tal como: ID de thread, um conjunto de registros, pilha de tempo de execução, errno, palavra de máscara de sinal, prioridade de agendamento

  • Pode fazer uso total do número de processadores paralelos

    • Para vários processadores, se houver apenas um processo, então apenas um processador estará funcionando e os outros processadores ficarão ociosos, o que é um desperdício de recursos para os processadores. Portanto, se você puder dividir o que um processo deve fazer, dividi-lo em vários e entregá-lo aos threads para fazer isso, ele fará uso total dos recursos do multi-processador

  • Enquanto aguarda o fim da operação de E / S lenta, o programa pode realizar outras tarefas

    • Por exemplo, para uma NetEase Cloud Music, você pode baixar uma música e depois ouvi-la. Este é um fenômeno causado por vários tópicos.

  • Aplicativos de computação intensiva, a fim de serem capazes de funcionar em um sistema multi-processador, o cálculo é dividido em vários threads para atingir

    • Para aplicações computacionalmente intensivas, a fim de melhorar sua eficiência computacional, o trabalho computacional pode ser dividido em várias partes, e então essas partes podem ser entregues a diferentes threads para cálculo, e várias threads são colocadas em diferentes processadores. O cálculo da memória pode melhorar a eficiência

  • Para aplicativos de I / O intensivos, a fim de melhorar o desempenho, as operações de I / O podem ser sobrepostas e os threads podem esperar por diferentes operações de I / O ao mesmo tempo

 

1.5 Desvantagens dos fios

  • Perda de desempenho

  • Robustez reduzida

    • Para um programa multi-threaded, a possibilidade de efeitos adversos devido a pequenos erros na alocação de tempo ou compartilhamento de variáveis ​​que não deveriam ser compartilhadas é muito alta.

  • Falta de controle de acesso

    • O processo é a granularidade básica do controle de acesso. Chamar certas funções do sistema operacional em um thread afetará todo o processo

  • Maior dificuldade de programação

    • É muito mais complicado escrever e depurar um programa multithread do que um programa single-threaded

 

2. Controle de linha

Para controle de thread, devemos primeiro entender que estamos usando a biblioteca de thread POSIX.

  • Os nomes da maioria das funções da biblioteca de threads começam com "pthread"

  • Para usar esta biblioteca de threads, o arquivo de cabeçalho phread.h deve ser introduzido

  • A biblioteca de função de thread de link é usar o comando-l pthread

 

 

2.1 ID do processo e ID do thread

  • No Linux. A implementação de thread atual é Native POSIX Thread Libaray, ou NPTL para breve. Nesta implementação, a thread também é conhecida como Light Weighted Process. Cada thread de modo de usuário corresponde a esta entidade de agendamento no kernel e também tem seu próprio descritor de processo (estrutura task_struct)

  • Antes dos threads, um processo corresponde a um descritor de processo no kernel. (1: 1)

  • Mas com threads, um processo corresponde a vários descritores de kernel. (1: N)

  • Mas para cada thread para chamar getpid para retornar o mesmo ID de processo, como resolver o problema acima?

Grupo de discussão:

struct task_struct
{
   ...
   pid_t pid;
   tid_t tgid;
   ...
   struct task_struct *group_leader;
   ...
   struct list_head thread_group;
   ...
};
  • Processos multi-threaded também são chamados de grupos de threads.Cada thread no grupo de threads tem um descritor de processo (task_struct) correspondente a ele no kernel.

  • O pid na estrutura do descritor do processo corresponde ao ID do thread

  • O tgid no descritor do processo corresponde ao ID do processo

Modo de usuário Chamada de sistema Estrutura correspondente do descritor de processo do kernel
ID do tópico gettid (void) pid_t pid
Id de processo getpid (void) pid_t tgid

Para o ID do segmento aprendido agora, é diferente do ID do segmento da biblioteca de segmentos POSIX. O tipo do ID do segmento é pthread_t. O ID do segmento e o ID do processo podem representar exclusivamente o segmento ou processo.

  • Como verificar o ID de um tópico?

ps -L

A opção -L mostrará

  • LWP: ID do thread, que é o valor de retorno da chamada do sistema gettid ()

  • NLWP: o número de threads no grupo de threads

Para o ID de thread, o Linux fornece uma chamada de sistema gettid para retornar o ID, mas glibc não encapsula a chamada de sistema para membros usarem em uma interface aberta.

O ID do tópico pode ser obtido das seguintes maneiras

#include <sys/syscall.h>
pid_t tid;
tid = syscall(STS_gettid);

O primeiro encadeamento no grupo de encadeamentos é chamado de encadeamento principal no modo do usuário e o líder do grupo no kernel. Quando o kernel cria o primeiro encadeamento, o ID do grupo de encadeamentos é definido como o encadeamento do primeiro ID, o ponteiro do líder do grupo aponta para si mesmo, o descritor do processo do thread principal.

Há um thread ID igual ao ID do processo no grupo de threads, e este thread é o thread principal do grupo de threads

【Nota】:

Threads e processos não são iguais, o processo tem um processo-pai de conceito, mas no grupo de threads dentro, todos os threads são relacionamentos de pares

2.2 Thread ID e layout do espaço de endereço do processo

  • A função pthread_create gera um ID de thread, que é armazenado no endereço apontado pelo primeiro parâmetro.

  • O ID do encadeamento é diferente do ID do encadeamento mencionado anteriormente.

    • O ID do encadeamento anterior pertence à categoria de agendamento de processo. Como um thread é um processo leve e a menor unidade do agendador do sistema operacional, um valor é necessário para representar o thread de maneira exclusiva.

    • O ID do thread gerado pela função pthread_create pertence à categoria da biblioteca de threads NPTL. O ID do thread é usado para outras funções da biblioteca de threads para usar o ID do thread para operar o thread

  • Biblioteca de thread NPTL, fornece a função pthread_self, você pode obter o ID do próprio thread

pthread_t pthread_self(void);
  • Para o tipo de pthread_t, na verdade, o ID do thread de pthread_t é essencialmente um endereço no espaço de endereço do processo

Como mostrado:

 

  • mmap: Você pode mapear um arquivo ou outro objeto na memória. Se o arquivo não for a soma do tamanho de várias páginas, o espaço não utilizado da última página será apagado.

 

3. Criar discussão

功能:创建一个新的线程
​
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
​
参数:
    thread:返回线程的ID
    attr:设置线程的属性,attr为NULL表示使用默认的属性
    start_routine:是一个函数地址,线程启动要执行的函数
    arg:传给线程启动函数的参数
​
返回值:
    成功返回0,失败返回错误码

Detecção de erro:

A diferença entre a função pthread e outras funções é que um erro retorna um código de erro

Para pthreads, ele também tem sua própria variável errno para suportar o código que usa errno. Mas para o erro da função pthread, geralmente é julgado pelo valor de retorno, porque o custo de leitura do valor de retorno é muito menor do que o custo de leitura do valor da variável errno no encadeamento

Exemplo:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
​
void* routine(void* arg)
{
  int i = 10;
  while (i > 0)
  {
    std::cout << (char*)arg << '\n';
    i--;
  }
  return NULL;//线程终止
}
​
int main()
{
​
  pthread_t tid;
  int ret = pthread_create(&tid, NULL, routine, (void*)("thread1"));
  if (ret > 0)
  {
    std::cout << "pthread_create error!" << '\n';
  }
​
  sleep(1);
  int i = 30;
  while (i > 10)
  {
    std::cout << "I am main thread!" << '\n';
    i--;
  }
  std::cout << "main exit!" << '\n';
  return 0;
}

 

3. Terminação de thread

Se você apenas encerrar um thread sem encerrar todo o processo, existem quatro métodos, como segue:

  • Retorne da função thread.

    • Este método não é aplicável ao thread principal, porque o retorno da função principal é equivalente a chamar exit

  • Um thread pode chamar pthread_cancel para encerrar outro thread no mesmo processo

  • O thread pode chamar a função pthread_exit para se encerrar

  • Você pode chamar a função pthread_kill para encerrar um tópico

Se qualquer thread chamar exit, _exit e Exit, todo o processo será encerrado.

 

3.1 pthread_exit

功能:线程终止
​
void pthread_exit(void* retval);
​
参数:
    retval:对于是joinable的线程,是其他线程调用pthread_join函数所得到的输出型参数的返回值
​
返回值:
    无返回值,和进程一样,线程结束无法返回到他的调用者

Para retval:

Este é um parâmetro de saída, a informação retornada pela terminação do thread é colocada no espaço de memória apontado por retval

 

3.2 pthread_cancel

功能:向线程发送取消请求
​
int pthread_cancel(pthread_t thread);
​
参数:
    thread:需要取消的线程的ID
    
返回值:
    成功返回0,失败返回错误码

 

3,3 pthread_kill

功能:向一个线程发送一个信号
​
#include <signal.h>
​
int pthread_kill(pthread_t thread, int sig);
​
参数:
    thread:线程ID(pthread_create函数的输出型参数)
    sig:信号的编号
    
返回值:
    成功返回0,失败返回错误码,并且没有信号发送

Exemplo:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
​
pthread_t main_tid;
int retval;
​
void* routine(void* arg)
{
 pthread_cancel(main_tid);
 int i = 100000;
 pthread_detach(pthread_self());//自我分离
 while (1)
 {
   std::cout << (char*)arg << ":" << i <<'\n';
   i--;
 }
 pthread_exit((void*)&retval);
 //return NULL;//线程终止
}
​
int main()
{
 main_tid = pthread_self();
 pthread_t tid;
 int ret = pthread_create(&tid, NULL, routine, (void*)("thread1"));
 if (ret > 0)
 {
   std::cout << "pthread_create error!" << '\n';
 }
​
 sleep(5);
 int i = 30;
 while (i > 10)
 {
   std::cout << "I am main thread!" << '\n';
   i--;
 }
 std::cout << "main exit!" << '\n';
​
 while (1)
 {
   std::cout << "asd" << '\n';
 }
}
  • Este programa mostra que, após a saída do encadeamento principal, o encadeamento filho não precisa necessariamente sair , porque o processo não é encerrado , portanto, o encadeamento filho pode continuar em execução.

【Nota】:

A saída de um encadeamento pode afetar outro encadeamento porque a chave é ver se o encadeamento existente permitiu a saída do processo.Se o processo também sair, todos os outros encadeamentos devem sair.

Porque os threads são dependentes do processo

 

  • Para tópicos

  • Quando o kernel envia um sinal para a thread por meio do comando kill , o kernel adiciona o sinal a todo o grupo de threads por padrão

  • Portanto, para distinguir o sinal enviado para o processo ou o sinal enviado para o encadeamento, existem dois conjuntos de signal_pending em task_struct , um conjunto é comum ao grupo de encadeamento e o outro conjunto é para um único encadeamento .

  • Quando o sinal enviado por kill é colocado no signal_pengding compartilhado pelo grupo de threads , ele pode ser processado por qualquer thread . O sinal enviado por pthread_kill é colocado no signal_pending privado da thread e só pode ser processado pela thread.

4. Espera e separação da linha

4.1 thread em espera

Por que precisamos esperar por tópicos ? (Semelhante ao processo)

  • O segmento que saiu não liberou seu espaço e ainda está no espaço de endereço do processo

  • O novo thread criado não volta para reutilizar o espaço de endereço que acabou de sair

功能:等待线程结束
​
int pthread_join(pthread_t thread, void** retval);
​
参数:
    thread:线程的ID
    retval:他指向一个指针,后者指向线程的
    
返回值:
    成功返回0,失败返回错误码

O encadeamento que chama esta função será suspenso (espera de bloqueio) até que o encadeamento com o encadeamento de ID termine. O encadeamento encerra de maneiras diferentes e o status de encerramento obtido por meio de pthraed_join é diferente.

  • Se o thread do thread retornar por retorno , a unidade apontada por retval armazena o valor de retorno da função do thread do thread

  • Se o thread do thread for encerrado de forma anormal por outro thread chamando pthread_cancel , a unidade apontada por rerval armazena a constante PTHREA_CANCELED

  • Se o thread do thread for encerrado chamando pthread_exit , a unidade apontada por retval armazena os parâmetros de pthread_exit

  • Se você não se preocupa com o status de encerramento do encadeamento , pode passar NULL para o parâmetro retval

  • Se o thread de discussão for encerrado chamando pthread_kill , a unidade apontada por retval armazena um valor aleatório

 

4.2 Separação de fios

  • Por padrão, os threads criados são todos juntáveis ​​(combinados) , após a saída do thread, ela precisa ser uma operação pthread_join , caso contrário não será capaz de liberar recursos, resultando em vazamento de memória

  • Se você não se preocupa com o valor de retorno do thread, a junção é um fardo. Neste momento, podemos dizer ao sistema para liberar automaticamente os recursos do thread quando o thread sair.

int pthread_detach(pthread_t thread);
​
int pthread_detach(pthread_self());

Podem ser outros threads no grupo de threads para separar o thread de destino ou o thread pode se separar

【Nota】:

juntável e separação é conflito, um thread é juntável nem isolado

#include <iostream>
#include <pthread.h>
#include <unistd.h>
​
void* routine(void* arg)
{
  pthread_detach(pthread_self());
  std::cout << (char*)arg << '\n';
  return NULL;
}
​
int main()
{
  pthread_t tid;
  if (pthread_create(&tid, NULL, routine, (void*)("thread 1")) > 0)
  {
    std::cout << "pthread_create error!" << '\n';
  }
  sleep(1);
​
  int ret = 0;
  if (pthread_join(tid, NULL) == 0)
  {
    std::cout << "wait success!" << '\n';
  }
  else 
  {
    std::cout << "wait error!" << '\n';
    ret = 1;
  }
  
​
  return ret;
}

Resultado:

thread 1
wait error
  • Explique que pthread_join e pthread_deatach não podem ser usados ​​ao mesmo tempo, isso é um conflito

 

5. Sincronização de thread e exclusão mútua

  • Na maioria dos casos, as variáveis ​​usadas pelos threads são variáveis ​​locais, e o espaço de endereço da variável está no espaço do thread. Nesse caso, a variável pertence a uma única variável e outros threads não podem obter essa variável.

  • Mas, às vezes, muitas variáveis ​​precisam ser compartilhadas dentro dos encadeamentos. Essas variáveis ​​são chamadas de variáveis ​​compartilhadas e a interação de vários encadeamentos pode ser concluída por meio do compartilhamento de dados.

  • A operação simultânea de variáveis ​​compartilhadas por vários threads causará alguns problemas. Ou seja, é o problema do erro no reparo dos dados?

#include <iostream>
#include <pthread.h>
#include <unistd.h>
​
//出现6的情况是这样的,有可能下面创建线程创建到第六个出错的时候,i已经变成6了,然后对于buyTicket函数再次从内存中取值的时候,所以取到的就是6了
//多个线程来的时候是对这个函数的重入
void* buyTicket(void *arg)
{
  int ticket = 20;
  while (ticket > 0)
  {
    usleep(1000);
    ticket--;
    std::cout << (char*)(arg) << "buy ticket!Have Ticket:" << ticket << std::endl;
  }
  
  return NULL;//线程结束
}
​
int main()
{
  pthread_t tid[6];
  //不能用这种方法创建,因为buyTicket读取的是地址值,就会导致,每次读取的都是最新的i值,不会看到以前的线程了
  //创建了五个子线程
  //for (int i = 1; i < 6; i++)
  //{
  //  if (pthread_create(tid + i, NULL, buyTicket, (void*)(&(i))) > 0)
  //  {
  //    std::cout << "pthread_create error!" << std::endl;
  //  }
​
  //  sleep(1);
  //}
  if (pthread_create(tid + 1, NULL, buyTicket, (void*)("1")) > 0)
  {
    std::cout << "pthread_create error!" << std::cout;
  }
  if (pthread_create(tid + 2, NULL, buyTicket, (void*)("2")) > 0)
  {
    std::cout << "pthread_create error!" << std::cout;
  }
  if (pthread_create(tid + 3, NULL, buyTicket, (void*)("3")) > 0)
  {
    std::cout << "pthread_create error!" << std::cout;
  }
  if (pthread_create(tid + 4, NULL, buyTicket, (void*)("4")) > 0)
  {
    std::cout << "pthread_create error!" << std::cout;
  }
  if (pthread_create(tid + 5, NULL, buyTicket, (void*)("5")) > 0)
  {
    std::cout << "pthread_create error!" << std::cout;
  }
  
  for (int i = 1; i <= 5; i++)
  {
    if (pthread_join(tid[i], NULL) == 0)
    {
      std::cout << i << " thread quit!" << std::endl;
    }
    sleep(1);
  }
  
  return 0;
}
  • Depois que a condição de julgamento while for verdadeira, o código pode mudar para outros processos simultaneamente

  • Para o último hibernar, o processo pode ser suspenso, o que significa que outros threads podem ter tempo suficiente para entrar na área crítica, e muitos threads podem entrar na área crítica.

  • --numEm si não é uma operação atômica

- A operação não é atômica , mas corresponde a três instruções de montagem

  • Etapa 1: carregar a variável num em um registro na memória

  • Etapa 2: atualize o valor no registro e execute a operação -1

  • Etapa 3: Escreva o novo valor do registro de volta para o espaço de memória da variável num

Para resolver os problemas acima, três coisas precisam ser feitas:

  • O código deve ter comportamento mutuamente exclusivo: quando o código entra na área crítica para execução, outros processos não podem entrar na área crítica

  • Se vários threads exigirem a execução do código na seção crítica ao mesmo tempo, e não houver nenhum thread para executar na seção crítica neste momento, apenas um thread pode ter permissão para entrar na seção crítica

  • Se o thread não for executado na seção crítica, então o thread não pode impedir que outros threads entrem na seção crítica

Como mostrado:

 

5.1 Existem duas maneiras de inicializar o mutex:

  • Método um: alocação estática

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • Método dois: alocação dinâmica

int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr* restrict attr);
​
参数:
    mutex:要初始化的互斥量
    attr:NULL

 

5.2 destruir mutex

Destruir mutex

Precisa prestar atenção para destruir mutex:

  • Mutex inicializado com PTHREAD_MUTEX_INITALIZER não precisa ser destruído

  • Não destrua um mutex bloqueado

  • O mutex que foi destruído, certifique-se de que nenhum thread tentará travar novamente

int pthread_mutex_destory(pthread_mutex_t* mutex);

 

5.3 Bloqueio e desbloqueio de Mutex

int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
​
返回值:
    返回值为0,失败返回错误号

Ao chamar pthead_lock, você pode encontrar as seguintes situações:

  • O mutex está em um estado desbloqueado, esta função irá bloquear o mutex e retornar com sucesso

  • Quando a chamada de função é iniciada, outros threads bloquearam o mutex, ou existem outros threads aplicando-se ao mutex ao mesmo tempo, mas não há competição para o mutex, então a chamada pthread_lock será bloqueada, esperando que o mutex seja desbloqueado

 

Acho que você gosta

Origin blog.csdn.net/qq_40399012/article/details/84206623
Recomendado
Clasificación