Νήμα (υπολογιστές)

Στην πληροφορική, ένα νήμα εκτέλεσης (Αγγλικά: thread) είναι η μικρότερη ακολουθία προγραμματισμένων εντολών που μπορεί να υποστεί διαχείριση ανεξάρτητα από το λειτουργικό σύστημα. Ένα νήμα είναι μια ελαφριά διεργασία. Η υλοποίηση των νημάτων και των διεργασιών διαφέρει από το ένα λειτουργικό σύστημα στο άλλο. Στις περισσότερες όμως περιπτώσεις ένα νήμα εμπεριέχεται σε μια διεργασία. Μπορούν να υπάρχουν πολλαπλά νήματα μέσα στην ίδια διεργασία, τα οποία μπορούν να μοιράζονται πόρους από το σύστημα, όπως μνήμη. Διαφορετικές διεργασίες δεν μπορούν να μοιράζονται τους ίδιους πόρους. Συγκεκριμένα, τα νήματα μιας διεργασίας περιέχουν τις εντολές προς την εκτελούμενη διεργασία (δηλαδή τον κώδικα της) και το εννοιολογικό της πλαίσιο (οι τιμές των μεταβλητών της σε οποιαδήποτε χρονική στιγμή.

Σε έναν απλό επεξεργαστή, η πολυνημάτωση (multithreading) πραγματοποιείται με τη μέθοδο της πολυπλεξίας με διαίρεση χρόνου (όπως στην πολυεπεξεργασία): ο επεξεργαστής μεταπηδάει μεταξύ των διάφορων νημάτων. Αυτή η εναλλαγή μεταξύ των διεργασιών ονομάζεται Μεταγωγή περιβάλλοντος (Αγγλικά: context switch) , και συμβαίνει σε πολύ τακτά χρονικά διαστήματα, τέτοια έτσι ώστε ο χρήστης έχει την εντύπωση ότι τα νήματα εκτελούνται την ίδια στιγμή. Μόνο σε έναν επεξεργαστή με πολλούς επεξεργαστικούς πυρήνες, τα νήματα εκτελούνται πραγματικά ταυτόχρονα και κάθε πυρήνας εκτελεί ένα συγκεκριμένο νήμα ή εργασία.   

Η υποστήριξη για νήματα ποικίλει ανάμεσα στις γλώσσες προγραμματισμού: κάποιες γλώσσες απλά δεν υποστηρίζουν την εκτέλεση παραπάνω του ενός νήματος για ένα πρόγραμμα ταυτόχρονα. Παραδείγματα τέτοιων γλωσσών είναι η Python και η OCaml οι οποίες παρόλο που έχουν βιβλιοθήκες για δημιουργία νημάτων δεν εκτελούνται τα νήματα ταυτόχρονα  . Άλλες γλώσσες περιορίζονται στη χρήση νημάτων χρήστη τα οποία δεν είναι ορατά στο πυρήνα και έτσι δεν μπορούν να εκτελεστούν ταυτόχρονα. Μόνο τα νήματα πυρήνα που είναι ορατά στον χρονοδρομολογητή του πυρήνα, μπορούν να εκτελεστούν ταυτόχρονα.

Πολλά σύγχρονα λειτουργικά συστήματα υποστηρίζουν τόσο νήματα καταμερισμού χρόνου, όσο και νήματα παράλληλης πολυεπεξεργασίας, στον χρονοδρομολογητή τους. Ο πυρήνας ενός λειτουργικού συστήματος επιτρέπει στους προγραμματιστές να χειρίζονται τα νήματα μέσω της διεπαφής κλήσεων συστήματος.  Ορισμένες υλοποιήσεις λέγονται νήμα πυρήνα, ενώ ελαφρά διεργασία είναι ο ειδικός τύπος νήματος πυρήνα που μοιράζεται την ίδια κατάσταση και πληροφορίες. Στα συστήματα unix υπάρχει το POSIX στάνταρντ για δημιουργία POSIX νημάτων.

 Πως διαφέρει ένα νήμα από μια διεργασία 
Ένα νήμα διαφέρει από μια διεργασία ενός πολυεπεξεργαστικού λειτουργικού συστήματος στα εξής:

 οι διεργασίες είναι τυπικώς ανεξάρτητες, ενώ τα νήματα αποτελούν υποσύνολα μιας διεργασίας.
 οι διεργασίες περιέχουν σημαντικά περισσότερες πληροφορίες κατάστασης από τα νήματα, ενώ πολλαπλά νήματα μιας διεργασίας μοιράζονται την κατάσταση της διεργασίας όπως επίσης μνήμη και άλλους πόρους.
 οι διεργασίες έχουν ξεχωριστούς χώρους διευθυνσιοδότησης, ενώ τα νήματα μοιράζονται το σύνολο του χώρου διευθύνσεων που τους παραχωρείται.
 η εναλλαγή ανάμεσα στα νήματα μιας διεργασίας είναι πολύ γρηγορότερη από την εναλλαγή ανάμεσα σε διαφορετικές διεργασίες.

Ένα πρόγραμμα που μεταγλωττίζεται δημιουργείται ένα εκτελέσιμο αρχείο. Αυτό όταν εκτελείται δημιουργείται μια νέα διεργασία μέσα στην οποία τρέχει ο εκτελέσιμος κώδικας. Το λειτουργικό σύστημα φροντίζει να δημιουργήσει χώρο μνήμης-διευθύνσεων για την διεργασία. Στο unix κάθε εκτέλεση του εκτελέσιμου αρχείου δημιουργεί μια νέα ξεχωριστή διεργασία η οποία τρέχει παράλληλα (ή ψευδοπαράλληλα με την τεχνική διαίρεσης χρόνου - όταν έχουμε ένα επεξεργαστή). Το νήμα είναι μια πολύ πιο "ελαφριά" μορφή διεργασίας όπου η δημιουργία είναι εκατοντάδες φορές πιο γρήγορη από την δημιουργία μιας διεργασίας. Τα νήματα τρέχουν παράλληλα όπως και οι διεργασίες. Η ιδέα είναι ότι δημιουργούμε μια διεργασία και μέσα από αυτή δημιουργούμε νήματα τα οποία έχουν πρόσβαση στο χώρο μνήμης της διεργασίας.   Ας δούμε ένα παράδειγμα POSIX νήματος το οποίο μεταγλωττίζεται στο unix με την gcc thread.c -o thread -lpthread:

/* thread.c */
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h> 
#include <stdio.h>

void *thread_function(void *arg) {
  int i;
  for ( i=0; i<20; i++ ) {
    printf("Το νήμα λέει γεια!\n");
    sleep(1);
  }
  return NULL;
}

int main(void) {

  pthread_t mythread;

  if ( pthread_create( &mythread, NULL, thread_function, NULL) ) {
    printf("σφάλμα κατά την δημιουργία του νήματος.");
    abort();
  }

  if ( pthread_join ( mythread, NULL ) ) {
    printf("σφάλμα κατά την pthread_join.");
    abort();
  }

  exit(0);

}

Στο συγκεκριμένο παράδειγμα έχουμε ένα πρόγραμμα το οποίο μόλις εκτελεστεί το λειτουργικό σύστημα δημιουργεί την διεργασία μέσα στην οποία τρέχει. Στην συνέχεια το πρόγραμμα δημιουργεί ένα νήμα μέσω της συνάρτησης pthread_create( &mythread, NULL, thread_function, NULL) το οποίο εμφανίζει το μήνυμα Το νήμα λέει γεια! 20 φορές με καθυστέρηση 1 δευτερόλεπτο κάθε φορά στην οθόνη. Παρόλο που μέσα στο πρόγραμμα δημιουργήσαμε μόνο ένα νήμα, στην πράξη τρέχουν δύο νήματα. Η συνάρτηση main() τρέχει μέσα στην διεργασία ως νήμα και στην συνέχεια δημιουργείται το δεύτερο νήμα μέσω της pthread_create. Η pthread_create "σπάει" το κύριο νήμα main σε δύο νήματα που τρέχουν παράλληλα. Η συνάρτηση pthread_join παίρνει ως παράμετρο tο νήμα που δημιουργήσαμε μόλις ολοκληρώσει το "ενώνει" με το κύριο νήμα main. Η συνάρτηση pthread_join λειτουργεί και ως εκκαθαριστής των νημάτων. Αν συνεχώς δημιουργούμε νήματα χωρίς να τρέχουμε την pthread_join το σύστημα θα φτάσει στον μέγιστο αριθμό επιτρεπόμενων νημάτων και η pthread_create δεν θα μπορεί να δημιουργεί νέα νήματα.   

 Πολυνημάτωση 
Η πολυνημάτωση (Αγγλικά: multithreading) αποτελεί ένα ευρέως διαδεδομένο μοντέλο προγραμματισμού και εκτέλεσης διεργασιών το οποίο επιτρέπει την ύπαρξη πολλών νημάτων μέσα στα πλαίσια μιας και μόνο διεργασίας. Τα νήματα αυτά μοιράζονται τους πόρους της διεργασίας και μπορούν να εκτελούνται ανεξάρτητα. Η πολυνημάτωση δεν πρέπει να μπερδεύεται με την πολυδιεργασία (Αγγλικά: multitasking) όπου έχει να κάνει με την εκτέλεση προγραμμάτων δηλαδή διεργασιών παράλληλα σε ένα υπολογιστικό σύστημα με πολλούς επεξεργαστές. Το πλεονέκτημα ενός πολυνηματικού προγράμματος είναι ότι του επιτρέπει να εκτελείται γρηγορότερα σε υπολογιστικά συστήματα που έχουν πολλούς επεξεργαστές, επεξεργαστές με πολλούς πυρήνες, ή κατά μήκος μιας συστοιχίας υπολογιστών. Για να μπορούν να χειραγωγηθούν σωστά τα δεδομένα, τα νήματα θα πρέπει ορισμένες φορές να συγκεντρωθούν σε ένα ορισμένο χρονικό διάστημα έτσι ώστε να επεξεργασθούν τα δεδομένα στη σωστή σειρά. Ένα ακόμα πλεονέκτημα της πολυδιεργασίας (και κατ επέκταση της πολυνημάτωσης) ακόμα και στους απλούς επεξεργαστές ( με έναν πυρήνα επεξεργασίας) είναι η δυνατότητα για μια εφαρμογή να ανταποκρίνεται άμεσα. Έχοντας ένα πρόγραμμα που εκτελείται μόνο σε ένα νήμα, αυτό θα φαίνεται ότι κολλάει ή ότι παγώνει, στις περιπτώσεις που εκτελείται κάποια μεγάλη διεργασία που απαιτεί πολύ χρόνο. Αντίθετα σε ένα πολυνηματικό σύστημα, οι χρονοβόρες διεργασίες μπορούν να εκτελούνται παράλληλα με άλλα νήματα που φέρουν άλλες εντολές για το ίδιο πρόγραμμα, καθιστώντας το άμεσα ανταποκρίσιμο. 

Τα λειτουργικά συστήματα προγραμματίζουν τα νήματα με έναν από τους δύο παρακάτω τρόπους:
 Πολυδιεργασία προτίμησης (Αγγλικά: Preemptive multitasking): γενικώς θεωρείται η καλύτερη προσέγγιση. Σύμφωνα με αυτή, το λειτουργικό σύστημα καθορίζει πότε θα γίνεται μια εναλλαγή από το ένα νήμα στο άλλο. Μειονέκτημα αυτής της μεθόδου είναι ότι το σύστημα μπορεί να πραγματοποιήσει μια εναλλαγή σε ακατάλληλη στιγμή με αρνητικές επιπτώσεις για τις εφαρμογές που εκτελούνται. 
 Πολυνημάτωση συνεργασίας (Αγγλικά: Cooperative multithreading): Σύμφωνα με αυτή τη προσέγγιση, τα ίδια τα νήματα αναλαμβάνουν τον έλεγχο. Έτσι όταν ένα νήμα τελειώσει, παραδίδει τους πόρους εκτέλεσης σε άλλο νήμα. Μειονέκτημα αυτής της προσέγγισης είναι ότι ένα νήμα μπορεί να περιμένει επ’ αόριστον να ελευθερωθούν πόροι συστήματος. 

Μέχρι και τα τέλη της δεκαετίας του 1990, οι πιο δημοφιλείς επεξεργαστές των προσωπικών υπολογιστών δεν υποστήριζαν πολλαπλά νήματα εκτέλεσης - κάποια λειτουργικά συστήματα υποστήριζαν τη χρήση νημάτων στο λογισμικό, που δεν εκτελούνταν πραγματικά παράλληλα αλλά από τον χρονοπρογραμματιστή του συστήματος, και είχαν εφαρμογές κυρίως στην επεξεργασία δεδομένων. Ωστόσο, επεξεργαστές σε άλλα ενσωματωμένα συστήματα που είχαν υψηλές απαιτήσεις για ανταπόκριση σε πραγματικό χρόνο (όπως π.χ. φανάρια σηματοδότησης, ελεγκτές εργοστασιακών μηχανών κ.α.) υποστήριζαν μια μορφή πολυνημάτωσης. Με το πέρας της δεκαετίας του 1990, η ιδέα της ταυτόχρονης εκτέλεσης εντολών σε πολλαπλά νήματα, γνωστή ως ταυτόχρονη πολυνημάτωση, υλοποιήθηκε και στους προσωπικούς υπολογιστές με το λανσάρισμα των επεξεργαστών Pentium 4 της Intel, με την ονομασία Υπερνημάτωση. Η απόγονη αρχιτεκτονική πού ακολούθησε τους Pentium 4 (εμπορικά προϊόντα Core και Core 2) δεν την ενσωμάτωνε. Από το 2008 και με την έλευση της αρχιτεκτονικής Nehalem και μέχρι σήμερα, η υπερνημάτωση επανεμφανίστηκε με βελτιώσεις στις σειρές επεξεργαστών Intel Core i5 και Core i7.

 Διεργασίες, νήματα πυρήνα και νήματα χρήστη 

Ένα νήμα χρήστη (Αγγλικά: user thread) είναι συνδεδεμένο με το εκτελέσιμο κώδικα που τρέχει στην διεργασία και δημιουργείται από το χρήστη χρησιμοποιώντας μια βιβλιοθήκη δημιουργίας νημάτων (όπως τα POSIX νήματα που δημιουργούνται από την βιβλιοθήκη pthread). Η εναλλαγή των νημάτων μιας διεργασίας γίνεται από την ίδια την διεργασία ενώ ο πυρήνας του λειτουργικού συστήματος αναλαμβάνει μόνο την εναλλαγή των διεργασιών. Κατά την εναλλαγή των νημάτων μέσα στην διεργασία αναλαμβάνει η διεργασία και δεν γίνονται κλήσεις συστήματος πυρήνα. Ένα μειονέκτημα των νημάτων χρήστη είναι ότι όταν ένα νήμα κολλήσει τότε ο πυρήνας του λειτουργικού συστήματος βλέπει μόνο την διεργασία που τρέχει όχι το προβληματικό νήμα. Ένα νήμα πυρήνα (Αγγλικά: kernel thread) αποτελεί την ελαφρύτερη μονάδα που μπορεί να δρομολογηθεί υλοποιείται και τρέχει μέσα στον πυρήνα του λειτουργικού συστήματος. Η δημιουργία-εκκίνηση αλλά και ο τερματισμός ενός νήματος πυρήνα γίνεται με κατευθείαν κλείσεις συστήματος μέσα στον πυρήνα.  Ανάλογα με το λειτουργικό σύστημα διαφέρει το μοντέλο που ακολουθείται στην υλοποίηση νημάτων. Παρακάτω βλέπουμε τα μοντέλα :

 Πολλά σε Ένα: πολλά νήματα χρήστη αντιστοιχίζονται σε ένα νήμα πυρήνα - τα πράσινα νήματα. 
 Ένα προς Ένα: ένα νήμα χρήστη αντιστοιχίζεται σε ένα νήμα πυρήνα. 
 Πολλά προς Πολλά: πολλά νήματα χρήστη να αντιστοιχιστούν σε πολλά νήματα πυρήνα. 

Ta Πράσινα νήματα είναι νήματα χρήστη που υλοποιούνται σε επίπεδο εφαρμογής και δεν χρησιμοποιούνται κλήσεις συστήματος μέσα στο πυρήνα σε αντίθεση με τα χαμηλού επιπέδου νήματα naive threads τα οποία χρησιμοποιούν κλήσεις συναρτήσεων πυρήνα. 

 Δείτε επίσης 
 Πράσινα νήματα
 POSIX νήματα

 Εξωτερικοί σύνδεσμοι 
 Binildas C. A., "Query by Slice, Parallel Execute, and Join: A Thread Pool Pattern in Java"
 The C10K problem
 Herb Sutter, "The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software"
 Edward Lee, "The Problem with Threads"
 Daniel Robbins, POSIX threads explained
 Debugging and Optimizing Multithreaded OpenMP Programs By Andrey Karpov and Evgeniy Romanovsky, March 02, 2009

 Παραπομπές 


Κατηγορία:Προγραμματισμός