- p°edchozφ Φlßnek - nßsledujφcφ Φlßnek - obsah - ·vodnφ strßnka -

LinuxovΘ noviny 08-09/98

Linux a vlßkna

Vladimφr Michl, 7. srpna 1998

Tento Φlßnek si klade za ·kol seznßmit Φtenß°e s vlßkny v Linuxu a jejich pou╛itφm, p°φpadn∞ s tφm, Φeho by se m∞l Φlov∞k p°i pou╛φvßnφ vlßken vyvarovat.

Ale od zaΦßtku. Nejprve je t°eba osv∞tlit rozdφl mezi termφnem proces a vlßkno.

Jako proces je v systΘmu chßpßn souhrn k≤du programu, dat programu, zßsobnφku, ·daj∙ o procesem otev°en²ch souborech, a takΘ informacφ ohledn∞ zpracovßnφ signßl∙. Tyto v╣echny informace mß ka╛d² proces vlastnφ (privßtnφ) a nem∙╛e je sdφlet s jin²m procesem, krom∞ datov²ch oblastφ. P°i volßnφ jßdra fork(2) se pak tyto informace pro nov² proces skopφrujφ, tak╛e jsou pro n∞j zase privßtnφ.

Jako vlßkno si m∙╛eme p°edstavit odlehΦen² proces, tj. pouze k≤d vlßkna a zßsobnφk, v╣e ostatnφ je sdφleno s ostatnφmi vlßkny tΘho╛ procesu. Vlßkno je tedy podmno╛inou procesu a proces m∙╛e vlastnit n∞kolik vlßken. Vlßkno samo o sob∞ v systΘmu existovat nem∙╛e, musφ k n∞mu v╛dy existovat proces, se kter²m sdφlφ v╣echna data, otev°enΘ soubory, zpracovßnφ signßl∙.

Pro implementaci vlßken existujφ tyto modely:

  • one-to-one - Implementace provedena na ·rovni jßdra. Ka╛dΘ vlßkno je pro jßdro samostatn² proces, plßnovaΦ proces∙ neΦinφ rozdφl mezi vlßknem a procesem. Nev²hodou tohoto modelu m∙╛e b²t velkß re╛ie p°i p°epφnßnφ vlßken.

  • many-to-one - Implementace provedena na ·rovni u╛ivatele, program si sßm implementuje vlßkna a v╣e okolo. Jßdro o vlßknech v procesech nemß ani tu╣enφ. Tento model se nehodφ na vφceprocesorovΘ systΘmy, proto╛e vlßkna nemohou b∞╛et zßrove≥ (ka╛dΘ na jinΘm procesoru), jeden proces nelze nechat vykonßvat na dvou procesorech. V²hodou m∙╛e b²t malß re╛ie p°epφnßnφ vlßken.

  • many-to-many - Implementace provedena na ·rovni jßdra i u╛ivatele. Tento model eliminuje nev²hody p°edchozφch implementacφ (velkß re╛ie p°i p°epφnßnφ proces∙, soub∞╛n∞ nem∙╛e b∞╛et vφce vlßken) a je proto pou╛it v mnoha komerΦnφch UNIXech (Solaris, Digital Unix, IRIX).

V Linuxu je pou╛it model prvnφ. Nev²hoda velkΘ re╛ie v podstat∞ nenφ, proto╛e p°epφnßnφ proces∙ je v Linuxu implementovßno velmi efektivn∞. Pro tvorbu proces∙ a vlßken se v Linuxu pou╛φvß volßnφ jßdra clone(2), kterΘ ale pou╛φvajφ pouze knihovny obhospoda°ujφcφ vlßkna.

V zaΦßtcφch, kdy se v Unixech zaΦala vlßkna objevovat, m∞l ka╛d² unixov² operaΦnφ systΘm jinΘ aplikaΦnφ rozhranφ pro prßci s vlßkny, a proto byly programy ╣patn∞ p°enositelnΘ. Proto vznikla norma POSIX, kterß mimo jinΘ takΘ definuje aplikaΦnφ rozhranφ pro prßci s vlßkny (POSIX 1003.1c). Toto POSIXovΘ rozhranφ je dostupnΘ i na OS Solaris 2.5, Digital Unix 4.0, IRIX 6. S ka╛dou distribucφ Linuxu postavenou na glibc-2.0 je dodßvßna knihovna pthread, kterß prßv∞ toto POSIXovΘ aplikaΦnφ rozhranφ implementuje.

Tvorba vlßken a jejich ukonΦenφ

Pro vytvo°enφ a ukonΦenφ vlßkna lze pou╛φt nßsledujφcφ funkce:

int  pthread_create(pthread_t * thread, 
 pthread_attr_t * attr,
 void * (*start_routine)(void *), void * arg);

  • funkce vytvo°φ novΘ vlßkno, kterΘ bude vykonßvat funkci start_routine, co╛ je funkce akceptujφcφ jeden parametr typu void *. Na adresu thread je ulo╛en identifikßtor vlßkna a jako atributy vlßkna m∙╛eme uvΘst NULL pro implicitnφ hodnoty.

void pthread_exit(void *retval);

  • tato funkce p°edΦasn∞ ukonΦφ vlßkno, ze kterΘho byla funkce zavolßna.

Vlßkno se takΘ ukonΦφ, skonΦφ-li funkce start_routine. V obou p°φpadech se p°edßvß nßvratov² k≤d.

int pthread_join(pthread_t th, 
  void **thread_return);

  • funkce Φekß na ukonΦenφ vlßkna th. Na adresu thread_return je ulo╛en nßvratov² k≤d vlßkna.

P°φklad, jak funkce pou╛φt, naleznete na v²pise P°φklad pou╛itφ vlßken.

#include <pthread.h>
#include <stdio.h>
#define ITEMS 10000

void * process(void *a){
      int i;
      printf("Process %s: start\n", (char *)a);
      for (i = 0; i<ITEMS; i++){
            printf("%s", (char *)a);
      };
      printf("Process %s: end\n", (char *)a);
      return NULL;
}

int main(){
      int retcode;
      pthread_t a,b;
      void * retval;

      retcode = pthread_create(&a, NULL, process, "A");
      if (retcode != 0) fprintf(stderr, "create a failed %d\n", retcode);
      retcode = pthread_create(&b, NULL, process, "B");
      if (retcode != 0) fprintf(stderr, "create b failed %d\n", retcode);
      retcode = pthread_join(a, &retval);
      if (retcode != 0) fprintf(stderr, "join a failed %d\n", retcode);
      retcode = pthread_join(b, &retval);
      if (retcode != 0) fprintf(stderr, "join b failed %d\n", retcode);
      return 0;
}

V²pis Φ. 1: P°φklad pou╛itφ vlßken

P°eklad programu

P°i p°eklßdßnφ programu, kter² pou╛φvß vlßkna, je t°eba tomuto p°izp∙sobit hlaviΦkovΘ soubory knihovny glibc tak, aby byly reentrantnφ. To provedeme definovßnφm makra _REENTRANT. Dßle je t°eba program slinkovat s knihovnou pthread. Pro p°eklad programu na v²pise P°φklad pou╛itφ vlßken pou╛ijeme:

 gcc -D_REENTRANT -o example1 example1.c -lpthread

KritickΘ sekce pomocφ mutexu

Nejprve si polo╛me otßzku, co je to kritickß sekce. Za kritickou sekci pova╛ujeme tu Φßst k≤du vlßkna, kterß operuje nad sdφlen²mi daty a hrozφ, ╛e paraleln∞ m∙╛e jinΘ vlßkno operovat nad stejn²mi daty. D∙sledkem m∙╛e b²t nekonzistence dat. Nap°φklad jedno vlßkno zv²╣φ sdφlenou prom∞nnou A o jedna a dßle s nφ poΦφtß, kde╛to druhΘ vlßkno prom∞nou A zmen╣φ o dv∞ a dßle s nφ poΦφtß. Pokud se po╣t∞stφ, tak se instrukce mohou prolo╛it tak, ╛e ani jedno vlßkno nedß sprßvn² v²sledek. Tomuto je t°eba zabrßnit a to tφm, ╛e do tΘ Φßsti, kterß pracuje s prom∞nnou A m∙╛e vstoupit pouze jedno vlßkno, druhΘ musφ Φekat a╛ to prvnφ skonΦφ. TakovΘto kritickΘ sekce, kde m∙╛e b²t v jednom okam╛iku pouze jedno vlßkno, naz²vßme MUTEX (MUTual EXclusion). Mutex mß dva stavy - zamΦen² (locked - n∞kterΘ vlßkno je uvnit°) a odemΦen² (unlocked - v mutexu nikdo nenφ).

Pro prßci s mutexy pou╛ijeme funkce:

int pthread_mutex_init(pthread_mutex_t *mutex, 
       const  pthread_mutexattr_t *mutexattr);

  • inicializace mutexu

int pthread_mutex_lock(pthread_mutex_t *mutex);

  • zamΦenφ mutexu. Po nßvratu je mutex v╛dy zamΦen pro vlßkno, kterΘ tuto funkci vykonalo. Pokud je mutex ji╛ zamΦen, funkce pozastavφ vlßkno a Φekß na odemΦenφ mutexu, aby nßsledn∞ mutex zamkla a mohla nechat vlßkno pokraΦovat.

int pthread_mutex_trylock(pthread_mutex_t *mutex);

  • pokus o zamΦenφ mutexu. Pokud je mutex ji╛ zamΦen, funkce se vrßtφ s chybou EBUSY.

int pthread_mutex_unlock(pthread_mutex_t *mutex);

  • odemΦenφ mutexu

int pthread_mutex_destroy(pthread_mutex_t *mutex);

  • uvoln∞nφ zdroj∙ spojen²ch s mutexem

V p°φkladu SchematickΘ znßzorn∞nφ pou╛itφ mutexu m∙╛ete vid∞t pou╛itφ mutexu.

pthread_mutex_t mut_var;
...
/* Inicializace mutexu */
      pthread_mutex_init(&mut_var, NULL);
...
/* Vstup do mutexu */
      pthread_mutex_lock(&mut_var);
/* Vykonßnφ operacφ nad sdφlen²mi daty */
...
/* V²stup z mutexu */
      pthread_mutex_unlock(&mut_var);
...
/* Na konci programu zru╣enφ mutexu */
      pthread_mutex_destroy(&mut_var);

V²pis Φ. 2: SchematickΘ znßzorn∞nφ pou╛itφ mutexu

U mutex∙ se m∙╛eme setkat s tφm, ╛e bude t°eba mutex zamknout v zßvislosti na podmφnce. Nap°φklad problΘm producent - konzument. Producent produkuje data do sdφlenΘ prom∞nnΘ a konzument je Φte. P°itom prom∞nnß musφ b²t zabezpeΦena mutexem a zßrove≥ se musφ hlφdat stav, zda prom∞nnß obsahuje u╛iteΦnß data. I na toto POSIX myslφ, a to pomocφ nßsledujφcφch funkcφ:

int pthread_cond_init(pthread_cond_t *cond, 
       pthread_condattr_t *cond_attr);

  • inicializace podmφnky

int pthread_cond_signal(pthread_cond_t *cond);

  • zp∙sobφ spu╣t∞nφ jednoho vlßkna, kterΘ Φekß na podmφnce. Jestli╛e neΦekß ╛ßdnΘ vlßkno, funkce nemß ╛ßdn² efekt. ╚ekß-li vφce vlßken, spustφ se pouze jedno, ale nenφ definovßno jakΘ.

int pthread_cond_broadcast(pthread_cond_t *cond);

  • zp∙sobφ spu╣t∞nφ v╣ech vlßken Φekajφcφch na podmφnce. Jestli╛e neΦekß ╛ßdnΘ vlßkno, funkce nemß ╛ßdn² efekt.

int pthread_cond_wait(pthread_cond_t *cond, 
       pthread_mutex_t *mutex);

  • automaticky odemkne mutex, pozastavφ vlßkno a Φekß na signßl od podmφnky. Po p°φchodu signßlu je mutex uzamΦen a tato funkce ukonΦena. Ka╛dß podmφnka musφ b²t uzav°ena v mutexu.

int pthread_cond_timedwait(pthread_cond_t *cond,
       pthread_mutex_t *mutex, 
       const struct timespec *abstime);

  • je podobnΘ pthread_cond_wait() s tφm rozdφlem, ╛e Φekßnφ je Φasov∞ omezeno. Pokud Φas vypr╣φ, pak je sekce uzamΦena a funkce je ukonΦena s chybou ETIMEDOUT.

int pthread_cond_destroy(pthread_cond_t *cond); 

  • uvolnφ zdroje spojenΘ s podmφnkou

V p°φkladu Pou╛itφ mutexu v problΘmu producent - konzument m∙╛ete vid∞t pou╛itφ mutexu a podmφnek na problΘmu producent - konzument. V╣imn∞te si rozdφlnΘ inicializace podmφnek, samoz°ejm∞ ob∞ podmφnky jdou inicializovat stejn²m zp∙sobem.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define ITEMS 100
#define NONVALID 0
#define VALID 1
pthread_mutex_t mut_var;
pthread_cond_t condvalid;
pthread_cond_t condnonvalid = PTHREAD_COND_INITIALIZER;
int valid;
int share;

void * konzument(void *a){
      printf("Process %s: start\n", (char *)a);
      while(1){
            pthread_mutex_lock(&mut_var);
            if (!valid)
              pthread_cond_wait(&condvalid, &mut_var);
            valid = NONVALID;
            printf("Process %s: %i\n", (char *)a, share);
            if (share == -1){
                  pthread_mutex_unlock(&mut_var);
                  break;
            };
            pthread_cond_signal(&condnonvalid);
            pthread_mutex_unlock(&mut_var);
      };
      printf("Process %s: end\n", (char *)a);
      return NULL;
}

void * producent(void *a){
      int i;
      printf("Process %s: start\n", (char *)a);
      for (i = 0; i<ITEMS; i++){
            pthread_mutex_lock(&mut_var);
            if (valid) 
              pthread_cond_wait(&condnonvalid, &mut_var);
            share = (int)rand();
            if (share == -1) share = 0;
            if (i == ITEMS - 1) share = -1;
            printf("Process %s: %i\n", (char *)a, share);
            valid = VALID;
            pthread_cond_signal(&condvalid);
            pthread_mutex_unlock(&mut_var);
      };
      printf("Process %s: end\n", (char *)a);
      return NULL;
}

int main(){
      pthread_t a,b;
      pthread_mutex_init(&mut_var, NULL);
      pthread_cond_init(&condvalid, NULL);
      pthread_create(&a, NULL, producent, "producent");
      pthread_create(&b, NULL, konzument, "konzument");
      pthread_join(a, NULL);
      pthread_join(b, NULL);
      pthread_cond_destroy(&condvalid);
      pthread_cond_destroy(&condnonvalid);
      pthread_mutex_destroy(&mut_var);
      return 0;
}

V²pis Φ. 3: Pou╛itφ mutexu v problΘmu producent - konzument

KritickΘ sekce pomocφ semafor∙

Semafory se pou╛φvajφ pro podobn² ·Φel jako mutexy, a to pro kontrolovßnφ vstupu do kritick²ch sekcφ. Ale na rozdφl od mutexu, kdy v sekci m∙╛e b²t pouze jeden, se semafory lze docφlit, ╛e v sekci m∙╛e b²t vφce vlßken. Semafor si m∙╛eme p°edstavit jako poΦφtadlo s poΦßteΦnφ hodnotou, kterou nastavφ u╛ivatel. V╛dy p°i vstupu do kritickΘ sekce se Φekß, dokud nenφ hodnota semaforu v∞t╣φ ne╛ nula. Pokud je, pak se hodnota zmen╣φ o jednu a vstoupφ se do kritickΘ sekce. Na konci sekce se hodnota semaforu o jedniΦku zvedne. Pro prßci se semafory pou╛φvßme funkce:

int sem_init(sem_t *sem, int pshared, 
       unsigned int value);

  • inicializace semaforu. Argument pshared urΦuje, zda je semafor lokßlnφ pro tento proces (hodnota 0) nebo je sdφlen mezi procesy (hodnota != 0). V Linuxu jsou podporovßny pouze lokßlnφ semafory.

int sem_wait(sem_t * sem);

  • slou╛φ pro vstup do kritickΘ sekce. Pokud je sekce obsazena (semafor == 0), pak se Φekß a╛ se sekce uvolnφ.

int sem_trywait(sem_t * sem);

  • slou╛φ pro vstup do kritickΘ sekce. Je-li sekce obsazena, funkce se vrßtφ s chybou EAGAIN.

int sem_post(sem_t * sem);

  • slou╛φ k ukonΦenφ kritickΘ sekce.

int sem_getvalue(sem_t * sem, int * sval);

  • vrßtφ hodnotu semaforu.

int sem_destroy(sem_t * sem);

  • uvolnφ v╣echny zdroje spojenΘ se semaforem.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#define ITEMS 100

sem_t semfull;
sem_t semempty;
int share;

void * konzument(void *a){
  printf("Process %s: start\n", (char *)a);
  while(1){
    sem_wait(&semfull);
    printf("Process %s: %i\n", (char *)a, share);
    if (share == -1){
      sem_post(&semempty);
      break;
    };
    sem_post(&semempty);
  };
  printf("Process %s: end\n", (char *)a);
  return NULL;
}

void * producent(void *a){
  int i;
  printf("Process %s: start\n", (char *)a);
  for (i = 0; i<ITEMS; i++){
    sem_wait(&semempty);
    share = (int)rand();
    if (share == -1) share = 0;
    if (i == ITEMS - 1) share = -1;
    printf("Process %s: %i\n", (char *)a, share);
    sem_post(&semfull);
  };
  printf("Process %s: end\n", (char *)a);
  return NULL;
}

int main(){
      pthread_t a,b;

      sem_init(&semfull, 0, 0);
      sem_init(&semempty, 0, 1);
      pthread_create(&a, NULL, producent,
          "producent");
      pthread_create(&b, NULL, konzument, 
          "konzument");
      pthread_join(a, NULL);
      pthread_join(b, NULL);
      sem_destroy(&semfull);
      sem_destroy(&semempty);
      return 0;
}

V²pis Φ. 4: Pou╛itφ semafor∙ u problΘmu producent - konzument

Toto rozhranφ pro semafory definuje norma POSIX 1003.1b a POSIX 1003.1i.

UkonΦenφ vlßkna jin²m vlßknem

int pthread_cancel(pthread_t thread);

  • vyvolß po╛adavek na zru╣enφ vlßkna.

int pthread_setcancelstate(int state,
      int *oldstate);

  • nastavφ chovßnφ vlßkna, kterΘ tuto funkci vyvolalo, na po╛adavek jeho zru╣enφ. Mo╛nΘ jsou dva stavy: PTHREAD_CANCEL_ENABLE a PTHREAD_CANCEL_DISABLE.

int pthread_setcanceltype(int type, int *oldtype);

  • nastavφ, kdy je mo╛no vlßkno zru╣it. Mo╛nΘ jsou dv∞ nastavenφ: PTHREAD_CANCEL_ASYNCHRONOUS - vlßkno bude zru╣eno skoro okam╛it∞ po p°ijetφ po╛adavku nebo PTHREAD_CANCEL_DEFERRED - vlßkno se zru╣φ a╛ v okam╛iku, kdy dojde do bodu, kde je mo╛no vlßkno zru╣it. Jako body jsou v POSIXu definovßny tyto funkce: pthread_join(3), pthread_cond_wait(3), pthread_cond_timedwait(3), pthread_testcancel(3), sem_wait(3), sigwait(3).

void pthread_testcancel(void);

  • tato funkce pouze testuje, zda byl p°ijat po╛adavek na zru╣enφ vlßkna. Pokud p°ijat byl, vlßkno je zru╣eno, v opaΦnΘm p°φpad∞ se funkce normßln∞ vrßtφ. Funkce se pou╛φvß v mφstech, kde jsou dlouhΘ kusy k≤du bez bod∙ vhodn²ch pro zru╣enφ.

Pokud je vlßkno v bodu vhodnΘm pro zru╣enφ (viz pthread_setcanceltype(3)) a p°ijalo po╛adavek na zru╣enφ, bude zru╣eno. TotΘ╛ se stane, pokud p°ijalo po╛adavek na zru╣enφ a a╛ nßsledn∞ vejde do bodu vhodnΘho pro zru╣enφ (pouze p°i nastavenΘm PTHREAD_CANCEL_DEFERRED).

Pokud se ukonΦφ hlavnφ vlßkno, ani╛ by poΦkalo na vlßkna jφm vytvo°enß, jsou tato vlßkna ukonΦena takΘ.

Jak pou╛φt funkce m∙╛ete vid∞t na p°φkladu UkonΦenφ vlßkna.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>

sem_t sem;

void * process(void *a){
   printf("Proces entered\n");
   sem_wait(&sem);
   printf("Proces exiting\n");
   return NULL;
}

int main(){
   pthread_t thid;
   void * thretval;

   sem_init(&sem, 0, 0);

   pthread_create(&thid, NULL, process, NULL);

   sleep(5); /* Vlßkno se zatφm rozb∞hne */ 
   if (pthread_cancel(thid) != 0)
     printf("Cancel error\n");

   pthread_join(thid[i], &thretval);
   if (thretval != PTHREAD_CANCELED)
     printf("Thread not be canceled.\n", i);

   return 0;
}

V²pis Φ. 5: UkonΦenφ vlßkna

Dal╣φ u╛iteΦnΘ funkce

pthread_t pthread_self(void);

  • vracφ identifikßtor vlßkna, kterΘ tuto funkci vyvolalo.

int pthread_equal(pthread_t thread1,
      pthread_t thread2);

  • porovnß, zda se identifikßtory vlßken rovnajφ.

int pthread_detach(pthread_t th);

  • odpojφ vlßkno. V╣echny pam∞╗ovΘ prost°edky, kterΘ vlßkno pou╛φvß, budou po ukonΦenφ vlßkna okam╛it∞ uvoln∞ny. S odpojen²m vlßknem se nelze synchronizovat a vyzvednout jeho nßvratov² k≤d funkcφ pthread_join(3).

int pthread_attr_init(pthread_attr_t *attr);

  • inicializuje objekt atribut∙ na implicitnφ hodnoty. Tento objekt se dß pou╛φt pro vytvo°enφ vφce vlßken.

int pthread_attr_destroy(pthread_attr_t *attr);

  • uvolnφ v╣echny prost°edky pot°ebnΘ pro objekt atribut∙.

ProblΘmy, do kter²ch se m∙╛ete dostat

  • knihovna pro vlßkna pou╛φvß signßly SIGUSR1 a SIGUSR2, proto je program pou╛φvat nem∙╛e.

  • pokud budete vlßkna pou╛φvat v X aplikacφch, je t°eba mφt Xlib kompilovßnu s -D_REENTRANT a podporou vlßken (knihovna musφ b²t napsßna reentrantn∞ - vφce vlßken m∙╛e vykonßvat tutΘ╛ funkci ve stejnou chvφli, bez vzßjemnΘho ovlivn∞nφ - funkce nepou╛φvajφ globßlnφ prom∞nnΘ). TotΘ╛ platφ o jakΘkoliv knihovn∞, kterou budete v programu pou╛φvat. Pokud knihovna takto reentrantnφ nenφ, je mo╛no ji pou╛φvat, ale pouze z hlavnφho vlßkna (k≤du procesu). Toto souvisφ s prom∞nnou errno. Ka╛dΘ vlßkno mß toti╛ vlastnφ, pouze hlavnφ vlßkno pou╛φvß globßlnφ errno.

  • pou╛φvßnφ vlßken v C++ s libg++ asi nebude fungovat. Pro pou╛φvßnφ vlßken v C++ je doporuΦen p°ekladaΦ egcs a knihovna libstdc++.

  • pokud program vytvo°φ nap°φklad 2 vlßkna, nedivte se, ╛e vidφte 4 stejnΘ procesy. Jeden je hlavnφ proces, pak vidφte 2 vlßkna a poslednφ je vlßkno starajφcφ se o sprßvn² chod vlßken. Toto vlßkno je vytvo°eno knihovnou pthread.

Odkazy

Na adrese http://www.serpentine.com/~bos/threads-faq/ lze najφt Φasto kladenΘ otßzky newsovΘ skupiny comp.programming.threads.

Bli╛╣φ informace o linuxov²ch vlßknech naleznete na adrese http://pauillac.inria.fr/~xleroy/linuxthreads. Lze zde takΘ najφt tutorial.

Na adrese http://www.rdg.opengroup.org/onlinepubs/7908799/index.html najdete X/Open Group Single Unix specification, kde by se m∞l takΘ dßt najφt bli╛╣φ popis aplikaΦnφho rozhranφ pro vlßkna.

Na adrese http://www.cs.wustl.edu/~schmidt/ACE.html najdete projekt, kter² takΘ usnad≥uje pou╛φvßnφ vlßken v C++. *


- p°edchozφ Φlßnek - nßsledujφcφ Φlßnek - obsah - ·vodnφ strßnka -