Quali risorse sono condivise tra i thread?


264

Di recente, in un'intervista, mi è stata posta una domanda qual è la differenza tra un processo e un thread. Davvero, non conoscevo la risposta. Ho pensato per un minuto e ho dato una risposta molto strana.

I thread condividono la stessa memoria, i processi no. Dopo aver risposto a questa domanda, l'intervistatore mi ha fatto un sorriso malvagio e mi ha fatto le seguenti domande:

Q. Conosci i segmenti in cui viene suddiviso un programma?

La mia risposta: sì (pensato che fosse facile) Stack, Data, Code, Heap

D. Allora, dimmi: quali segmenti condividono i thread?

Non ho potuto rispondere a questa domanda e ho finito per dire tutti.

Per favore, qualcuno può presentare le risposte corrette e impressionanti per la differenza tra un processo e un thread?


9
I thread condividono lo stesso spazio indirizzo virtuale , il processo no.
Benoit, il

Risposte:


177

Sei praticamente corretto, ma i thread condividono tutti i segmenti tranne lo stack. I thread hanno stack di chiamate indipendenti, tuttavia la memoria in altri stack di thread è ancora accessibile e in teoria potresti tenere un puntatore alla memoria nel frame dello stack locale di altri thread (anche se probabilmente dovresti trovare un posto migliore per mettere quella memoria!).


27
La parte interessante è che anche se i thread hanno stack di chiamate indipendenti, la memoria in altri stack è ancora accessibile.
Karthik Balaguru,

1
Sì, mi chiedo se sia accettabile accedere alla memoria in altre pile tra i thread? Finché sei sicuro di non provare a fare riferimento a uno stack che è stato deallocato, non sono sicuro di vedere un problema con esso?
bph

2
@bph: è possibile accedere alla memoria dello stack di un altro thread, ma nell'interesse di una buona pratica di ingegneria del software, non direi che è accettabile farlo.
Greg Hewgill,

1
L'accesso, in particolare la scrittura, alle pile di altri thread fa casini con diverse implementazioni di Garbage Collector. Questo potrebbe essere giustificato come un difetto dell'implementazione del GC.
yyny,

56

Da Wikipedia (penso che sarebbe un'ottima risposta per l'intervistatore: P)

Le discussioni differiscono dai tradizionali processi del sistema operativo multitasking in quanto:

  • i processi sono in genere indipendenti, mentre i thread esistono come sottoinsiemi di un processo
  • i processi contengono considerevoli informazioni sullo stato, mentre più thread all'interno di un processo condividono lo stato, nonché la memoria e altre risorse
  • i processi hanno spazi di indirizzi separati, mentre i thread condividono il loro spazio di indirizzi
  • i processi interagiscono solo attraverso meccanismi di comunicazione tra processi forniti dal sistema.
  • La commutazione del contesto tra thread nello stesso processo è in genere più rapida della commutazione del contesto tra processi.

2
al punto 2 precedente: per i thread anche la CPU mantiene un contesto.
Jack,

49

Qualcosa che ha davvero bisogno di essere sottolineato è che ci sono davvero due aspetti di questa domanda: l'aspetto teorico e l'aspetto delle implementazioni.

Innanzitutto, diamo un'occhiata all'aspetto teorico. È necessario comprendere cosa sia un processo concettualmente per comprendere la differenza tra un processo e un thread e ciò che è condiviso tra loro.

Abbiamo quanto segue dalla sezione 2.2.2 Il modello di thread classico nei moderni sistemi operativi 3e di Tanenbaum:

Il modello di processo si basa su due concetti indipendenti: raggruppamento ed esecuzione delle risorse. A volte è utile separarli; è qui che entrano in gioco i thread ....

Lui continua:

Un modo di vedere un processo è che è un modo per raggruppare le risorse correlate. Un processo ha uno spazio di indirizzi contenente testo e dati del programma, nonché altre risorse. Queste risorse possono includere file aperti, processi secondari, allarmi in sospeso, gestori di segnali, informazioni contabili e altro. Mettendoli insieme sotto forma di un processo, possono essere gestiti più facilmente. L'altro concetto che un processo ha è un thread di esecuzione, generalmente abbreviato in solo thread. Il thread ha un contatore di programmi che tiene traccia delle istruzioni da eseguire successivamente. Ha registri, che contengono le sue attuali variabili di lavoro. Ha uno stack, che contiene la cronologia di esecuzione, con un frame per ogni procedura chiamata ma da cui non è ancora stata restituita. Sebbene un thread debba essere eseguito in alcuni processi, il thread e il suo processo sono concetti diversi e possono essere trattati separatamente. I processi vengono utilizzati per raggruppare le risorse; i thread sono le entità pianificate per l'esecuzione sulla CPU.

Più in basso fornisce la seguente tabella:

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        |

Quanto sopra è ciò di cui hai bisogno per far funzionare i thread. Come altri hanno sottolineato, cose come i segmenti dipendono dai dettagli di implementazione dipendenti dal sistema operativo.


2
Questa è un'ottima spiegazione Ma probabilmente dovrebbe essere ricollegato alla domanda in qualche modo da essere considerato una "Risposta"
catalyst294,

Per quanto riguarda la tabella, il contatore del programma non è un registro? e lo "stato" di un thread, catturato nel valore dei registri? Mi manca anche il puntatore al codice che eseguono (puntatore al testo del processo)
onlycparra

29

Informa l'intervistatore che dipende interamente dall'implementazione del sistema operativo.

Prendi Windows x86 per esempio. Esistono solo 2 segmenti [1], codice e dati. Ed entrambi sono associati all'intero spazio degli indirizzi da 2 GB (lineare, utente). Base = 0, limite = 2 GB. Ne avrebbero creato uno ma x86 non consente a un segmento di essere sia di lettura / scrittura che di esecuzione. Quindi ne fecero due e misero CS in modo da indicare il descrittore di codice, e il resto (DS, ES, SS, ecc.) In modo da indicare l'altro [2]. Ma entrambi indicano le stesse cose!

La persona che ti sta intervistando ha fatto un'ipotesi nascosta che non ha dichiarato, e questo è uno stupido trucco da tirare.

Quindi per quanto riguarda

D. Allora dimmi quale segmento di thread condivide?

I segmenti sono irrilevanti per la domanda, almeno su Windows. Le discussioni condividono l'intero spazio degli indirizzi. C'è solo 1 segmento di stack, SS, e indica esattamente le stesse cose che fanno DS, ES e CS [2]. Cioè l'intero spazio utente insanguinato . 0-2GB. Ovviamente, ciò non significa che i thread abbiano solo 1 stack. Ovviamente ognuno ha il suo stack, ma i segmenti x86 non vengono utilizzati per questo scopo.

Forse * nix fa qualcosa di diverso. Chissà. La premessa su cui si basava la domanda era rotta.


  1. Almeno per lo spazio utente.
  2. Da ntsd notepad:cs=001b ss=0023 ds=0023 es=0023

1
Sì ... I segmenti dipendono dal sistema operativo e dal compilatore / linker. A volte esiste un segmento BSS separato dal segmento DATA. A volte c'è RODATA (dati come stringhe costanti che possono essere nelle pagine contrassegnate come Sola lettura). Alcuni sistemi suddividono persino i DATI in PICCOLI DATI (accessibili da un offset base + 16 bit) e (FAR) (offset 32 ​​bit necessario per accedere). È anche possibile che vi sia un ulteriore segmento TLS DATA (Thread Local Store) che viene generato in base al thread
Adisak,

5
Ah no! Stai confondendo segmenti con sezioni! Le sezioni indicano come il linker divide il modulo in parti (dati, dati, testo, bss, ecc.) Come descritto. Ma sto parlando di segmenti, come specificato nell'hardware intel / amd x86. Non collegato a compilatori / linker. Spero che abbia un senso.
Alex Budovski,

Tuttavia, Adisak ha ragione sul negozio Thread locale. È privato per il thread e non è condiviso. Sono a conoscenza del sistema operativo Windows e non sono sicuro di altri sistemi operativi.
Jack,

20

In genere, i thread sono chiamati processi leggeri. Se dividiamo la memoria in tre sezioni, sarà: Codice, dati e Stack. Ogni processo ha il proprio codice, i dati e le sezioni dello stack e, a causa di questo contesto, il tempo di commutazione è un po 'alto. Per ridurre il tempo di cambio di contesto, le persone hanno escogitato il concetto di thread, che condivide il segmento Dati e codice con altri thread / processi e ha il proprio segmento STACK.


Hai dimenticato il mucchio. Heap, se non sbaglio, dovrebbe essere condiviso tra i thread
Phate,

20

Un processo ha segmenti di codice, dati, heap e stack. Ora, l'istruzione Pointer (IP) di un thread OR thread punta al segmento di codice del processo. I segmenti di dati e heap sono condivisi da tutti i thread. E che dire dell'area dello stack? Qual è effettivamente l'area dello stack? È un'area creata dal processo solo per il suo thread da usare ... perché gli stack possono essere usati in un modo molto più veloce di heap ecc. L'area dello stack del processo è divisa tra thread, cioè se ci sono 3 thread, allora il l'area dello stack del processo è divisa in 3 parti e ciascuna è data ai 3 thread. In altre parole, quando diciamo che ogni thread ha il suo stack, quello stack è in realtà una parte dell'area dello stack del processo allocata a ciascun thread. Quando un thread termina la sua esecuzione, lo stack del thread viene recuperato dal processo. Infatti, non solo lo stack di un processo è diviso tra thread, ma tutti i set di registri che utilizza un thread come SP, PC e registri di stato sono i registri del processo. Quindi, quando si tratta di condivisione, le aree di codice, dati e heap sono condivise, mentre l'area dello stack è semplicemente divisa tra i thread.


13

I thread condividono il codice, i segmenti di dati e l'heap, ma non condividono lo stack.


11
C'è una differenza tra "in grado di accedere ai dati nello stack" e la condivisione dello stack. Quei thread hanno le loro pile che vengono spinte e spuntate quando chiamano metodi.
Kevin Peterson,

2
Sono entrambi ugualmente validi punti di vista. Sì, ogni thread ha il suo stack nel senso che esiste una corrispondenza uno a uno tra thread e stack e ogni thread ha uno spazio che utilizza per il suo normale utilizzo dello stack. Ma sono anche risorse di processo completamente condivise e, se lo si desidera, qualsiasi thread può accedere allo stack di qualsiasi altro thread con la stessa facilità.
David Schwartz,

@DavidSchwartz, posso riassumere il tuo punto come di seguito: ogni thread ha il suo stack e lo stack è composto da 2 parti: la prima parte condivisa tra thread prima che il processo sia multi-thread e la seconda parte che viene popolata quando il thread proprietario è in esecuzione .. Accetto?
FaceBro,

2
@nextTide Non ci sono due parti. Le pile sono condivise, punto. Ogni thread ha il suo stack, ma sono anche condivisi. Forse una buona analogia è se tu e tua moglie avete una macchina, ma potete usare le macchine a vicenda ogni volta che lo desiderate.
David Schwartz,

5

I thread condividono dati e codice mentre i processi no. Lo stack non è condiviso per entrambi.

I processi possono anche condividere la memoria, più precisamente il codice, ad esempio dopo un Fork(), ma si tratta di un dettaglio di implementazione e di un'ottimizzazione (del sistema operativo). Il codice condiviso da più processi sarà (si spera) duplicato alla prima scrittura nel codice - questo è noto come copia su scrittura . Non sono sicuro della semantica esatta per il codice dei thread, ma presumo un codice condiviso.

           Discussione di processo

   Stack privato privato
   Dati privati ​​condivisi
   Codice privato 1   condiviso 2

1 Il codice è logicamente privato ma potrebbe essere condiviso per motivi di prestazioni. 2 Non sono sicuro al 100%.


Direi che il segmento di codice (segmento di testo), diversamente dai dati, è quasi sempre di sola lettura sulla maggior parte delle architetture.
Jorge Córdoba,

4

Le discussioni condividono tutto [1]. C'è uno spazio di indirizzi per l'intero processo.

Ogni thread ha il proprio stack e registri, ma tutti gli stack dei thread sono visibili nello spazio degli indirizzi condiviso.

Se un thread alloca un oggetto nel suo stack e invia l'indirizzo a un altro thread, entrambi avranno uguale accesso a quell'oggetto.


In realtà, ho appena notato un problema più ampio: penso che stai confondendo due usi del segmento di parole .

Il formato del file per un eseguibile (ad es. ELF) ha sezioni distinte in esso, che possono essere indicate come segmenti, contenenti codice compilato (testo), dati inizializzati, simboli di linker, informazioni di debug, ecc. Non ci sono segmenti di heap o stack qui, poiché quelli sono costrutti solo di runtime.

Questi segmenti di file binari possono essere mappati separatamente nello spazio degli indirizzi di processo, con autorizzazioni diverse (ad es. Eseguibile di sola lettura per codice / testo e non eseguibile copia su scrittura per i dati inizializzati).

Le aree di questo spazio di indirizzi vengono utilizzate per scopi diversi, come allocazione di heap e stack di thread, per convenzione (imposto dalle librerie di runtime della lingua). Tuttavia, è tutta memoria, e probabilmente non segmentata, a meno che non sia in esecuzione in modalità 8086 virtuale. Lo stack di ogni thread è una porzione di memoria allocata al momento della creazione del thread, con l'attuale indirizzo superiore dello stack archiviato in un registro del puntatore dello stack e ogni thread mantiene il proprio puntatore dello stack insieme agli altri registri.


[1] OK, lo so: maschere di segnale, TSS / TSD ecc. Lo spazio degli indirizzi, compresi tutti i suoi segmenti di programma mappati, è comunque condiviso.


3

In un framework x86, si possono dividere più segmenti (fino a 2 ^ 16-1). Le direttive ASM SEGMENT / ENDS lo consentono e gli operatori SEG e OFFSET consentono l'inizializzazione dei registri di segmento. CS: gli IP sono generalmente inizializzati dal caricatore, ma per DS, ES, SS l'applicazione è responsabile dell'inizializzazione. Molti ambienti consentono le cosiddette "definizioni di segmento semplificate" come .code, .data, .bss, .stack ecc. E, a seconda anche del "modello di memoria" (piccolo, grande, compatto ecc.) Il caricatore inizializza i registri di segmento di conseguenza. Di solito .data, .bss, .stack e altri soliti segmenti (non lo faccio da 20 anni, quindi non ricordo tutto) sono raggruppati in un singolo gruppo - ecco perché di solito DS, ES e SS indicano stessa area, ma questo serve solo a semplificare le cose.

In generale, tutti i registri di segmento possono avere valori diversi in fase di esecuzione. Quindi, la domanda dell'intervista era corretta: quale CODICE, DATI e STACK sono condivisi tra i thread. La gestione dell'heap è qualcos'altro: è semplicemente una sequenza di chiamate al sistema operativo. E se non avessi affatto un sistema operativo, come in un sistema incorporato, puoi ancora avere nuovo / cancellare nel tuo codice?

Il mio consiglio ai giovani: leggi qualche buon libro di programmazione di assemblee. Sembra che i curricula universitari siano piuttosto poveri in questo senso.


2

Oltre alla memoria globale, i thread condividono anche una serie di altri attributi (ovvero, questi attributi sono globali per un processo, piuttosto che specifici per un thread). Questi attributi includono quanto segue:

  • ID processo e ID processo principale;
  • ID gruppo di processo e ID sessione;
  • terminale di controllo;
  • credenziali di processo (ID utente e gruppo);
  • descrittori di file aperti;
  • registra i blocchi creati usando fcntl();
  • disposizioni del segnale;
  • informazioni relative al file system: umask, directory di lavoro corrente e directory root;
  • timer intervallo ( setitimer()) e timer POSIX ( timer_create());
  • semadjValori undo ( ) del sistema V semaphore (Sezione 47.8);
  • limiti delle risorse;
  • Tempo CPU consumato (come restituito da times());
  • risorse consumate (come restituite da getrusage()); e
  • buon valore (impostato da setpriority() e nice()).

Tra gli attributi che sono distinti per ogni thread ci sono i seguenti:

  • ID thread (Sezione 29.5);
  • maschera di segnalazione;
  • dati specifici del thread (Sezione 31.3);
  • stack di segnali alternati (sigaltstack() );
  • la variabile errno;
  • ambiente a virgola mobile (vedi fenv(3) );
  • politica e priorità di programmazione in tempo reale (sezioni 35.2 e 35.3);
  • Affinità CPU (specifica per Linux, descritta nella Sezione 35.4);
  • capacità (specifiche per Linux, descritte nel Capitolo 39); e
  • stack (variabili locali e informazioni sul collegamento delle chiamate di funzione).

Estratto da: L'interfaccia di programmazione Linux: un manuale di programmazione del sistema Linux e UNIX, Michael Kerrisk , pagina 619


0

Thread condivide l'heap (esiste una ricerca sull'heap specifico del thread) ma l'implementazione corrente condivide l'heap. (e ovviamente il codice)


0

Nel processo tutti i thread condividono risorse di sistema come memoria heap ecc. Mentre Thread ha il proprio stack

Quindi il tuo ans dovrebbe essere memoria heap che tutti i thread condividono per un processo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.