Quais recursos são compartilhados entre os threads?


264

Recentemente, fui perguntado em uma entrevista qual é a diferença entre um processo e um tópico. Realmente, eu não sabia a resposta. Pensei por um minuto e dei uma resposta muito estranha.

Threads compartilham a mesma memória, processos não. Depois de responder a isso, o entrevistador me deu um sorriso maligno e disparou as seguintes perguntas para mim:

Q. Você conhece os segmentos nos quais um programa é dividido?

Minha resposta: sim (pensei que era fácil) Stack, Data, Code, Heap

P. Então, diga-me: quais segmentos os threads compartilham?

Não pude responder e acabei dizendo todas elas.

Por favor, alguém pode apresentar as respostas corretas e impressionantes para a diferença entre um processo e um tópico?


9
Os threads compartilham o mesmo espaço de endereço virtual , o processo não.
Benoit

Respostas:


177

Você está praticamente correto, mas os threads compartilham todos os segmentos, exceto a pilha. Os encadeamentos têm pilhas de chamadas independentes, no entanto, a memória em outras pilhas de encadeamentos ainda está acessível e, em teoria, você pode manter um ponteiro para a memória no quadro de pilha local de algum outro encadeamento (embora você provavelmente encontre um lugar melhor para colocar essa memória!).


27
A parte interessante é que, embora os threads tenham pilhas de chamadas independentes, a memória em outras pilhas ainda está acessível.
Karthik Balaguru

1
Sim - Gostaria de saber se é aceitável acessar a memória em outras pilhas entre threads? Contanto que você tenha certeza de que não está tentando fazer referência a uma pilha que foi desalocada, não tenho certeza se vejo algum problema com ela?
Bph

2
@ bph: é possível acessar a memória de pilha de outro thread, mas, no interesse das boas práticas de engenharia de software, eu não diria que é aceitável fazê-lo.
Greg Hewgill

1
O acesso, especialmente para gravação, às pilhas de outros threads prejudica várias implementações do coletor de lixo. Isso poderia ser justificado como uma falha na implementação do GC.
yyny 5/09/19

56

Da Wikipedia (acho que seria uma resposta muito boa para o entrevistador: P)

Os encadeamentos diferem dos processos tradicionais do sistema operacional multitarefa:

  • processos são normalmente independentes, enquanto os encadeamentos existem como subconjuntos de um processo
  • processos carregam informações consideráveis ​​sobre o estado, enquanto vários threads em um processo compartilham o estado, além de memória e outros recursos
  • processos têm espaços de endereço separados, enquanto os threads compartilham seu espaço de endereço
  • os processos interagem apenas através de mecanismos de comunicação entre processos fornecidos pelo sistema.
  • A alternância de contexto entre threads no mesmo processo geralmente é mais rápida que a alternância de contexto entre processos.

2
sobre o ponto 2 acima: Para threads, a CPU também mantém um contexto.
Jack

49

Algo que realmente precisa ser destacado é que há realmente dois aspectos nessa questão - o aspecto teórico e o de implementação.

Primeiro, vamos olhar para o aspecto teórico. Você precisa entender o que é um processo conceitualmente para entender a diferença entre um processo e um encadeamento e o que é compartilhado entre eles.

Temos o seguinte na seção 2.2.2 O modelo de encadeamento clássico nos sistemas operacionais modernos 3e de Tanenbaum:

O modelo de processo é baseado em dois conceitos independentes: agrupamento e execução de recursos. Às vezes é útil separá-los; é aqui que entram os tópicos ....

Ele continua:

Uma maneira de analisar um processo é que é uma maneira de agrupar recursos relacionados. Um processo possui um espaço de endereço que contém texto e dados do programa, além de outros recursos. Esses recursos podem incluir arquivos abertos, processos filhos, alarmes pendentes, manipuladores de sinais, informações contábeis e muito mais. Reunindo-os na forma de um processo, eles podem ser gerenciados mais facilmente. O outro conceito de um processo é um encadeamento de execução, geralmente encurtado para apenas encadeamento. O encadeamento possui um contador de programa que controla qual instrução executar a seguir. Possui registradores, que mantêm suas variáveis ​​de trabalho atuais. Ele possui uma pilha, que contém o histórico de execução, com um quadro para cada procedimento chamado, mas ainda não retornado. Embora um encadeamento deva ser executado em algum processo, o thread e seu processo são conceitos diferentes e podem ser tratados separadamente. Os processos são usados ​​para agrupar recursos; threads são as entidades agendadas para execução na CPU.

Mais abaixo, ele fornece a seguinte tabela:

Per process items             | Per thread items
------------------------------|-----------------
Address space                 | Program counter
Global variables              | Registers
Open files                    | Stack
Child processes               | State
Pending alarms                |
Signals and signal handlers   |
Accounting information        |

O acima é o que você precisa para que os threads funcionem. Como outros já apontaram, coisas como segmentos são detalhes de implementação dependentes do SO.


2
Esta é uma ótima explicação. Mas provavelmente deve ser amarrado para trás à pergunta de algum modo a ser considerado uma "resposta"
catalyst294

Em relação à tabela, o programa não é um contador? e o "estado" de um segmento, capturado no valor dos registros? Estou faltando também o ponteiro para o código que eles executam (ponteiro para o texto do processo)
onlycparra 21/01

29

Diga ao entrevistador que isso depende inteiramente da implementação do sistema operacional.

Veja o Windows x86, por exemplo. Existem apenas 2 segmentos [1], Código e Dados. E eles são mapeados para todo o espaço de endereço de 2 GB (linear, usuário). Base = 0, Limite = 2 GB. Eles teriam feito um, mas o x86 não permite que um segmento seja de leitura / gravação e execução. Então eles formaram dois e colocaram CS para apontar para o descritor de código, e o restante (DS, ES, SS, etc) para apontar para o outro [2]. Mas ambos apontam para a mesma coisa!

A pessoa que entrevistou você fez uma suposição oculta de que ele / ela não declarou, e esse é um truque estúpido.

Então, com relação a

P. Então, diga-me qual segmento de segmento compartilha?

Os segmentos são irrelevantes para a questão, pelo menos no Windows. Threads compartilham todo o espaço de endereço. Existe apenas um segmento de pilha, SS, e aponta exatamente para as mesmas coisas que DS, ES e CS fazem [2]. Ou seja, todo o maldito espaço do usuário . 0-2GB. Obviamente, isso não significa que os threads tenham apenas 1 pilha. Naturalmente, cada um tem sua própria pilha, mas os segmentos x86 não são usados ​​para essa finalidade.

Talvez o * nix faça algo diferente. Quem sabe. A premissa em que a pergunta se baseava foi quebrada.


  1. Pelo menos para o espaço do usuário.
  2. De ntsd notepad:cs=001b ss=0023 ds=0023 es=0023

1
Sim ... Segmentos depende do sistema operacional e do compilador / vinculador. Às vezes, há um segmento BSS separado do segmento DATA. Às vezes, há RODATA (dados como sequências constantes que podem estar em páginas marcadas como Somente leitura). Alguns sistemas até dividem DATA em SMALL DATA (acessível a partir de uma base + deslocamento de 16 bits) e (FAR) DATA (deslocamento de 32 bits necessário para acessar). Também é possível que exista um segmento TLS DATA (Thread Local Store) extra gerado por segmento
Adisak

5
Ah não! Você está confundindo segmentos com seções! As seções são como o vinculador divide o módulo em partes (dados, dados, texto, bss, etc.) como você descreveu. Mas estou falando de segmentos, conforme especificado no hardware intel / amd x86. Nada relacionado a compiladores / vinculadores. Espero que isso faça sentido.
Alex Budovski

No entanto, a Adisak está certa sobre a loja Thread Local. É privado para o segmento e não é compartilhado. Estou ciente do sistema operacional Windows e não tenho certeza de outro sistema operacional.
Jack

20

Geralmente, os Threads são chamados de processo leve. Se dividirmos a memória em três seções, será: Código, dados e Pilha. Todo processo tem seu próprio código, dados e seções de pilha e, devido a esse contexto, o tempo de troca é um pouco alto. Para reduzir o tempo de alternância de contexto, as pessoas criaram o conceito de encadeamento, que compartilha o segmento de dados e código com outro encadeamento / processo e possui seu próprio segmento STACK.


Você esqueceu a pilha. Heap, se não estou errado, deve ser compartilhada entre segmentos
Phate

20

Um processo possui segmentos de código, dados, pilha e pilha. Agora, o Ponteiro de Instrução (IP) de um thread OU threads aponta para o segmento de código do processo. Os segmentos de dados e heap são compartilhados por todos os threads. Agora, e a área da pilha? Qual é realmente a área da pilha? É uma área criada pelo processo apenas para que seu encadeamento seja usado ... porque as pilhas podem ser usadas de uma maneira muito mais rápida que as pilhas etc. A área de empilhamento do processo é dividida entre encadeamentos, ou seja, se houver três encadeamentos, A área da pilha do processo é dividida em 3 partes e cada uma é dada aos 3 threads. Em outras palavras, quando dizemos que cada thread tem sua própria pilha, essa pilha é na verdade uma parte da área da pilha do processo alocada para cada thread. Quando um encadeamento termina sua execução, a pilha do encadeamento é recuperada pelo processo. De fato, não apenas a pilha de um processo é dividida entre os threads, mas todo o conjunto de registros que um thread usa como SP, PC e state state são os registros do processo. Portanto, quando se trata de compartilhamento, as áreas de código, dados e heap são compartilhadas, enquanto a área de pilha é apenas dividida entre threads.


13

Os encadeamentos compartilham o código, os segmentos de dados e o heap, mas não compartilham a pilha.


11
Há uma diferença entre "poder acessar dados na pilha" e compartilhar a pilha. Esses threads têm suas próprias pilhas que são pressionadas e exibidas quando chamam métodos.
Kevin Peterson

2
Ambos são visões igualmente válidas. Sim, todo encadeamento possui sua própria pilha, no sentido de que há uma correspondência individual entre encadeamentos e pilhas e cada encadeamento possui um espaço usado para seu próprio uso normal da pilha. Mas eles também são recursos de processo totalmente compartilhados e, se desejado, qualquer thread pode acessar a pilha de qualquer outra thread tão facilmente quanto a sua.
David Schwartz

@DavidSchwartz, posso resumir seu argumento da seguinte forma: Cada thread tem sua própria pilha e a pilha consiste em 2 partes - a primeira parte que é compartilhada entre os threads antes do processo ser multiencadeado e a segunda parte que é preenchida quando o segmento proprietário está em execução .. Concorda?
FaceBro

2
@nextTide Não há duas partes. As pilhas são compartilhadas, ponto final. Cada thread tem sua própria pilha, mas também é compartilhada. Talvez uma boa analogia seja se você e sua esposa tiverem um carro, mas poderão usar os carros um do outro a qualquer momento.
David Schwartz

5

Os threads compartilham dados e código, enquanto os processos não. A pilha não é compartilhada para ambos.

Os processos também podem compartilhar memória, mais precisamente codificar, por exemplo, após a Fork(), mas este é um detalhe de implementação e otimização (do sistema operacional). O código compartilhado por vários processos (espero) será duplicado na primeira gravação no código - isso é conhecido como cópia na gravação . Não tenho certeza sobre a semântica exata do código de threads, mas assumo o código compartilhado.

           Thread de processo

   Stack private private
   Dados privados compartilhados
   Código privado 1   compartilhado 2

1 O código é logicamente privado, mas pode ser compartilhado por motivos de desempenho. 2 Não tenho 100% de certeza.


Eu diria que o segmento de código (segmento de texto), diferentemente dos dados, é quase sempre somente leitura na maioria das arquiteturas.
Jorge Córdoba

4

Tópicos compartilham tudo [1]. Há um espaço de endereço para todo o processo.

Cada encadeamento possui sua própria pilha e registradores, mas as pilhas de todos os encadeamentos são visíveis no espaço de endereço compartilhado.

Se um segmento alocar algum objeto em sua pilha e enviar o endereço para outro segmento, os dois terão acesso igual a esse objeto.


Na verdade, acabei de perceber um problema mais amplo: acho que você está confundindo dois usos da palavra segmento .

O formato do arquivo de um executável (por exemplo, ELF) possui seções distintas, que podem ser chamadas de segmentos, contendo código compilado (texto), dados inicializados, símbolos de vinculador, informações de depuração etc. Não há segmentos de pilha ou pilha aqui, já que essas são construções apenas em tempo de execução.

Esses segmentos de arquivos binários podem ser mapeados no espaço de endereço do processo separadamente, com permissões diferentes (por exemplo, executável somente leitura para código / texto e não-executável de copiar na gravação para dados inicializados).

Áreas desse espaço de endereço são usadas para diferentes propósitos, como alocação de heap e pilhas de encadeamentos, por convenção (imposta pelas bibliotecas de tempo de execução do idioma). É tudo apenas memória e provavelmente não é segmentado, a menos que você esteja executando no modo 8086 virtual. A pilha de cada encadeamento é um pedaço de memória alocado no momento da criação do encadeamento, com o endereço superior da pilha atual armazenado em um registro de ponteiro de pilha e cada encadeamento mantém seu próprio ponteiro de pilha junto com seus outros registradores.


[1] OK, eu sei: máscaras de sinal, TSS / TSD etc. O espaço de endereço, incluindo todos os seus segmentos de programa mapeados, ainda é compartilhado.


3

Em uma estrutura x86, é possível dividir o máximo de segmentos (até 2 ^ 16-1). As diretivas ASM SEGMENT / ENDS permitem isso, e os operadores SEG e OFFSET permitem a inicialização dos registros de segmento. CS: IP geralmente são inicializados pelo carregador, mas para DS, ES, SS o aplicativo é responsável pela inicialização. Muitos ambientes permitem as chamadas "definições de segmento simplificadas", como .code, .data, .bss, .stack etc. e, dependendo também do "modelo de memória" (pequeno, grande, compacto etc.), o carregador inicializa os registros de segmento adequadamente. Normalmente, dados, .bss, .stack e outros segmentos usuais (não faço isso há 20 anos, não lembro de todos) são agrupados em um único grupo - é por isso que geralmente DS, ES e SS apontam para o mesma área, mas isso é apenas para simplificar as coisas.

Em geral, todos os registradores de segmento podem ter valores diferentes no tempo de execução. Portanto, a pergunta da entrevista estava correta: qual dos CODE, DATA e STACK são compartilhados entre os threads. O gerenciamento de heap é outra coisa - é simplesmente uma sequência de chamadas para o sistema operacional. Mas e se você não tiver um sistema operacional, como em um sistema incorporado - você ainda pode ter um novo / excluir no seu código?

Meu conselho para os jovens - leia um bom livro de programação de montagem. Parece que os currículos universitários são bastante pobres nesse aspecto.


2

Além da memória global, os threads também compartilham vários outros atributos (ou seja, esses atributos são globais para um processo, e não específicos para um thread). Esses atributos incluem o seguinte:

  • ID do processo e ID do processo pai;
  • ID do grupo de processos e ID da sessão;
  • terminal de controle;
  • credenciais de processo (IDs de usuário e grupo);
  • descritores de arquivo aberto;
  • bloqueios de registro criados usando fcntl();
  • disposições de sinal;
  • informações relacionadas ao sistema de arquivos: umask, diretório de trabalho atual e diretório raiz;
  • temporizadores de intervalo ( setitimer()) e temporizadores POSIX ( timer_create());
  • Semáforo System V desfazer ( semadj) valores (Seção 47.8);
  • limites de recursos;
  • Tempo de CPU consumido (retornado por times());
  • recursos consumidos (retornados por getrusage()); e
  • bom valor (definido por setpriority()e nice()).

Entre os atributos que são distintos para cada encadeamento estão os seguintes:

  • ID do segmento (Seção 29.5);
  • máscara de sinal;
  • dados específicos de segmentos (Seção 31.3);
  • pilha de sinal alternativo ( sigaltstack());
  • a variável errno;
  • ambiente de ponto flutuante (consulte fenv(3));
  • política de agendamento em tempo real e prioridade (seções 35.2 e 35.3);
  • Afinidade da CPU (específica do Linux, descrita na Seção 35.4);
  • recursos (específicos do Linux, descritos no capítulo 39); e
  • pilha (variáveis ​​locais e informações de ligação de chamada de função).

Trecho de: Interface de programação Linux: Manual de programação de sistemas Linux e UNIX, Michael Kerrisk , página 619


0

Thread compartilha o heap (há uma pesquisa sobre heap específico de thread), mas a implementação atual compartilha o heap. (e, claro, o código)


0

No processo, todos os threads compartilham recursos do sistema, como heap Memory etc., enquanto o Thread tem sua própria pilha

Portanto, seu ans deve ser uma pilha de memória que todos os threads compartilhem para um processo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.