Hlavní navigace

Programování pod Linuxem pro všechny (15)

7. 5. 2004
Doba čtení: 4 minuty

Sdílet

Dnes si povíme něco o speciální datové oblasti vláken, která umožňuje vláknům mít vlastní kopii dat.

Data specifická pro vlákna

Všechna vlákna uvnitř jednoho programu, na rozdíl od procesů, sdílejí tentýž adresový prostor. Pokud tedy jedno vlákno modifikuje nějakou část paměti (např. globální proměnnou), je tato změna viditelná v ostatních vláknech. To umožňuje více vláknům operovat se stejnými daty bez vzájemné komunikace (tu proberu v některém z příštích dílů).

Každé vlákno má však svůj vlastní zásobník (call stack). Tím je zajištěno, že každé vlákno může realizovat svůj vlastní kód a volat různé další funkce podle svých potřeb. V programu s jediným vláknem platí, že každé volání podprogramu v každém vlákně má svoji vlastní množinu lokálních proměnných, jež jsou uloženy v zásobníku tohoto vlákna.

Někdy je požadováno, aby jistá proměnná byla duplicitně vytvořena tak, aby každé vlákno mělo svou vlastní kopii. Linux podporuje tento požadavek tím, že poskytuje každému vláknu datovou oblast specifickou pro vlákna. Proměnné uložené do této oblasti jsou duplikovány pro každé vlákno, a proto každé vlákno může takovou proměnnou modifikovat, aniž by se změnila její hodnota pro jiná vlákna. Protože jinak vlákna sdílejí tentýž paměťový prostor, nemohou být data ve speciální datové oblasti zpřístupňována normálním způsobem. Pro tento účel se používají speciální funkce, jež umožňují nastavovat a získávat data z této oblasti.

Speciální data pro vlákna musejí být typu void * a můžete jich vytvořit jakékoliv množství. Každá položka dat se zpřístupňuje pomocí klíče. K vytvoření klíče a tedy k vytvoření nové datové položky je určena funkce pthread_key_cre­ate(). Prvním argumentem je ukazatel na proměnnou pthread_key_t. Klíčová hodnota může být použita každým vláknem ke zpřístupnění své vlastní kopie datové položky. Druhý argument funkce pthread_key_cre­ate() je tzv. čistící funkce (cleanup function). Pokud pomocí druhého argumentu předáte ukazatel na funkci, provede se její kód a bude jí předána hodnota specifická pro tento klíč. Funkce se provede vždy, i když se vlákno zruší v nějakém náhodném bodě své realizace. Pokud čistící funkci nechcete používat, zadejte místo druhého argumentu NULL.

Po vytvoření klíče může každé vlákno nastavit svoji vlastní hodnotu odpovídající tomuto klíči prostřednictvím funkcepthread_set­specific(). Prvním argumentem této funkce je samotný klíč a druhým argumentem je speciální hodnota typu void *, která se má uložit. Ke zpětnému získání dat lze použít funkci pthread_getspe­cific(), které se jako argument předává klíč.

Předpokládejme například, že vaše aplikace má řešený problém rozdělen mezi několik vláken. Abychom si zachovali přehled, nechť každé vlákno má svůj vlastní protokol (log file), ve kterém je zaznamenáno, jak realizace vlákna pokračuje. Speciální datová oblast je ideálním místem, kam uložit ukazatel na protokol, a to pro každé vlákno.

Funkce main() v tomto příkladu vytváří klíč k uložení ukazatele na protokol a ukládá jej v proměnné thread_log_key. Protože se jedná o globální proměnnou, je sdílena všemi vlákny. Když každé vlákno začíná svoji realizaci prostřednictvím funkce vlákna, otevře protokol a ukládá ukazatel na soubor pod tímto klíčem. Později může každé z vytvořených vláken volat funkci write_to_thre­ad_log, pomocí níž zapisuje do protokolu zprávy pro konkrétní vlákno. Tato funkce zjistí ukazatel na soubor pro konkrétní vlákno ze speciální datové oblasti a zapíše zprávu.

CS24_early

#include <malloc.h>
#include <pthread.h>
#include <stdio.h>

/* Klic pouzivany k asociaci dat */
static pthread_key_t thread_log_key;

/* Zapise MESSAGE do logovaciho souboru pro vlakno */

void write_to_thread_log (const char *message)
{
  FILE *thread_log = (FILE *) pthread_getspecific(thread_log_key);
  fprintf(thread_log, "%s\n", message);
}

/* Zavre logovaci soubor vlakna */

void close_thread_log (void *thread_log)
{
  fclose((FILE *) thread_log);
}

void *thread_function (void *args)
{
  char thread_log_filename[20];
  FILE *thread_log;

  /* Generuje jmeno pro logovaci soubor vlakna */
  sprintf (thread_log_filename, "thread%d.log", (int) pthread_self());
  /* Otevre logovaci soubor */
  thread_log = fopen(thread_log_filename, "w");
  /* Ulozi ukazatel na soubor do specialni
     datove oblasti vlakna zpristupnovane
     pomoci thread_log_key */
  pthread_setspecific(thread_log_key, thread_log);

  write_to_thread_log("Thread starting.");

  /* Zde vlakno pokracuje ve sve realizaci a
     zapisuje informace do logovaciho souboru */

  return(NULL);
}

int main()
{
  int i;
  pthread_t threads[5];

  /* Vytvori klic pro asociaci ukazatelu na
     logovaci soubory v specialni datove
     oblasti. Jako cistici funkce se pouzije
     close_thread_log, ktera uzavira logovaci
     soubory. */
  pthread_key_create (&thread_log_key, close_thread_log);
  /* Vytvori vlakna */
  for (i = 0; i < 5; ++i)
    pthread_create (&(threads[i]), NULL, thread_function, NULL);
  /* Ceka na ukonceni vsech vlaken */
  for (i = 0; i < 5; ++i)
    pthread_join (threads[i], NULL);
  return(0);
} 

Všimněte si, že funkce thread_function() nepotřebuje uzavírat protokol. Je tomu tak proto, že při vytvoření klíče pro protokol byla funkce close_thread_log() specifikována jako čistící funkce pro tento klíč. Kdykoliv se vlákno ukončí, Linux zavolá funkci close_thread_log(), a ta se postará o uzavření protokolů.

To je pro dnešek vše, příště se budeme zabývat podrobněji čistícími funkcemi a povíme si něco o synchronizaci vláken.

Byl pro vás článek přínosný?

Autor článku