Szálak (Threads)

A szálak segítségével több, különálló végrehajtási egységre bonthatunk egy folyamatot.


 

  Definíció

  Műveletek a szálakkal

  Létrehozás (pthread_create)

  Várakozás (pthread_join)

  Leállítás (pthread_exit)

  Más szál leállítása (pthread_cancel)

  Szálak szinkronizálása

  Mutexek

  Mutex változó inicializálása

  Mutex változó törlése

  Mutex változó blokálása

  Mutex változó felszabadítása

  Feltételes változók

  Feltételes változók inicializálása

  Feltételes változók törlése

  Feltételes változók - várakozás valamilyen eseményre

  Feltételes változók - esemény bekövetkeztét jelző művelet(ek)

  Feltételes változó - példa

  Szemaforok

  Szemaforok inicializálása:

  Szemaforok törlése

  Szemafor értékének növelése (megfelel a V - signal - műveletnek)

  Szemafor értékének csökkentése (megfelel a P - wait - műveletnek)

  Olvasó/író (reader / writer) típusú blokálás

  Inicializálás

  A reader/writer objektum törlése

  Olvasó blokálás művelete

  Író blokálás szerepe

  Szinkronizálás példa

 


 

  Osztott memória (shared memory)

  Definíció

A szál a végrehajtás legkisebb egysége, amit az operációs rendszer ütemez. A szálak implementációja különböző operációs rendszereken eltérő lehet, viszont a szálak többnyire egy folyamat részét képezik. Több szál létezhet egy folyamaton belül amelyek megosztják az erőforrásokat, nem úgy mint a különböző folyamatok. Különösen érvényes a megosztás a forráskódra, amennyiben a szálak ugyanazon műveleteket végzik.

A szálak felügyelése és szinkronizálása teljes mértékben a programozó feladata. Sok esetben fontos lehet, hogy bár ugyanazon műveleteket végzik a különböző szálak, mégis meg kell várniuk, hogy az előtte futó szál befejezze a feladatát. Gondoljunk csak arra, hogy minden szál nyomtatni szeretne akkor előfordulhat, hogy elveszti az éppen nyomtató szál a vezérlést és az oldal felénél egy másik szál folytatja egy teljesen más dokumentum kinyomtatásával.

A szálak a következő dolgokat osztják meg egymás között:

- forráskódot

- adatokat

- megnyitott fájlokat (descriptorokat)

- jelzéseket

- az aktuális munkakatalógust

- felhasználó és csoport ID -t

Minden szálnak van:

- azonosítója

- regiszter készlet, verem mutató

- verem a helyi változóknak, visszatérési címek

- prioritásuk

- visszatérítési értékük

  Műveletek a szálakkal

  Létrehozás (pthread_create)

Az pthread_creat rendszerfüggvény létrehoz egy új végrehajtási szálat az éppen futó folyamatnak.
Alakja:

#include <pthread.h>

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

A thread változóba visszatérített érték határozza meg az azonosítót. Az attr változónak NULL értéket kell adni amennyiben az alapértelmezett thread tulajdonságokat szeretnénk használni. A void * (*start_routine) paraméter egy függvényre mutató pointer, amit a létrehozó szál fog végrehajtani. Ennek a függvénynek egyetlen void paramétere lehet. A *arg paraméter egy pointer amely az átadandó adatra mutat. Amennyiben több adatot szeretnénk átadni a függvénynek akkor egy struct -ra mutató pointert adjunk meg paraméterként.

  Várakozás (pthread_join)

ptread_join függvény hívás következtében a függvényt hívó szál leáll és megvárja a paraméterként megadott szál befejezését.
Szintaxisa:

int pthread_join(pthread_t thread, void **value_ptr);

A thread paraméterhez kerül a bevárandó szál azonosítója és a value_ptr paramétert általában NULL értékkel hívjuk meg. Meghívható bármilyen más értékkel és akkor az továbbítódik a ptread_exit() függvénynek.

  A szál leállítása

A ptread_exit függvény leállítja a szál futását és a paraméterként megadott visszatérési értekkel tér vissza.
Szintaxisa:

void pthread_exit(void * retval);

A retval paraméterrel fog visszatérni a szál befejezése után.

  Más szál leállítása (pthread_cancel)

A pthread_cancel függvény leállítja egy másik szál futását. Szintaxisa:

int pthread_cancel(pthread_t thread);

A thread paraméter egy másik azonosítóját kell tartalmazza.

  Szálak szinkronizálása

A szálakra (thread) jellemző szinkronizálási eszközök (objektumok) a következők: mutex változók, feltételes változók, szemaforok és író/olvasó(reader/writer) blokkolások. Mindegyik szinkronizációs változóhoz hozzá van rendelve egy várakozási sor, mely azokat a szálakat tartalmazza, amelyek blokkolva vannak az adott változóra vonatkozóan.

Bizonyos primitívek - amelyek megvizsgálják, hogy a szinkronizációs változók hozzáférhetőek-e - "felébresztik" mindegyik blokkolt szálat egy adott időpontban, kitörlik a várakozási sorból, és a vezérlésük a továbbiakban az ütemező egység hatáskörébe kerül. Meg kell jegyeznünk, hogy a szálak újraindítása tetszőleges sorrendben történik, vagyis nincs semmiféle összefüggés aközött, hogy milyen sorrendben blokkolódtak a folyamatok illetve, hogy milyen sorrendben kerülnek ki a várakozási sorból.

Fontos észrevétel! Abban az esetben, ha egy szinkronizácós objektum blokkolva van egy szál által, és ez a szál befejeződik anélkül, hogy felszabadítaná az illető objektumot, a szinkronizációs objektum blokkolva marad! Következésképpen megtörténhet, hogy az illető objektumon blokkolt szálak holtpontba kerülnek.

  Mutexek

  Mutex változó inicializálása

Egy mutex változó inicializálása történhet statikus vagy dinamikus módon, a következőképpen:

Posix, statikus inicializálás:

pthread_mutex_t MutexValtozoNeve = PTHREAD_MUTEX_INITIALIZER;

Solaris, statikus inicializálás:

mutex_t MutexValtozoNeve = 0;

A statikus inicializálás tehát egy standard értéknek a mutex változóhoz történő hozzárendelését feltételezi. A dinamikus inicializálás egy init típusú függvény meghívásával történik, melynek első argumentuma a mutex változóra mutató pointer.

Posix, dinamikus inicializálás:

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

A pthread_mutex_init meghívása a mutexattr paraméter által megadott attributumokkal inicializálja a mutex változót. Ennek a változónak a jelentőségét egy későbbi fejezetben mutatjuk be. Egyelőre tegyük fel, hogy ennek a paraméternek az értéke NULL, vagyis implicit attribútumokat rendelünk hozzá a mutex változóhoz.

Solaris, dinamikus inicializálás:

int mutex_init(mutex_t *mutex, int type, void *arg);

A mutex_init függvényhívás type paramétere a mutex változó láthatósági hatáskörét adja meg. Amennyiben az értéke USYNC_PROCESS, akkor több folyamatból is elérhető. Amennyiben USYNC_THREAD az értéke, a változó csak az aktuális folyamatból érhető el. Az arg paraméter későbbi fejlesztésekre van lefoglalva, ezért egyetlen elfogadott értéke NULL.

  Mutex változó törlése

Egy mutex változó törlése során az általa lefoglalva tartott erőforrások fel lesznek szabadítva. A mutex változónak deblokált állapotban kell lennie. A törlést végző függvények:

Posix:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

-------------------------------------

Solaris:

int mutex_destroy(mutex_t *mutex);

-------------------------------------

  Mutex változó blokálása

Egy mutex változó blokálása:

Posix:

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock (pthread_mutex_t *mutex);

Solaris:

int mutex_lock(mutex_t *mutex);

int mutex_trylock(mutex_t *mutex);

A lock hívások esetén, amennyiben a mutex változó nincs blokkolva egy másik szál által, akkor a hívó szál tulajdonává válik, és a függvény azonnal visszatér. Ha viszont már blokkolva van egy másik szál által, akkor a függvény végrehajtása felfüggesztődik, amíg a mutex változó fel nem szabadul.

A trylock hívások esetén a függvények azonnal visszatérnek, függetlenül attól, hogy a mutex változó blokkolva van már egy másik szál által vagy sem. Amennyiben a változó szabad, akkor a szál tulajdonává válik. Ha viszont egy másik (vagy éppen az aktuális) szál lefoglalva tartja, a függvény az EBUSY hibakóddal tér vissza.

  Mutex változó felszabadítása

Egy mutex változó felszabadítása a következőképpen történik:

Posix:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Solaris:

int mutex_unlock(mutex_t *mutex);

Feltételezzük, hogy a mutex változót az a szál foglalta le, amelyik a felszabadító függvényt meghívja.

  Feltételes változók

Bármely feltételes változó valamilyen esemény bekövetkeztére vár. Hozzá van rendelve egy mutex változó és egy predikátum. A predikátum tartalmazza azt a feltételt, melynek teljesülnie kell ahhoz, hogy az esemény bekövetkezzen, a mutex változó szerepe pedig az, hogy védje ezt a predikátumot. Az eseményre való várakozás, amelynek kapcsán a feltételes változót használjuk a következő “forgatókönyv” alapján történik:

A hozzárendelt mutex változó blokálása

Amíg (a predikátum hamis)

     Várakozás az illető feltételes változóhoz kapcsolódóan

Műveletek végrehajtása (esetlegesen)

Mutex változó felszabadítása

Figyelemre méltó, hogy a feltételes változóhoz kapcsolódó várakozás ideje alatt a rendszer felszabadítja a hozzárendelt mutex változót. Abban a pillanatban, amikor a feltétel bekövetkeztét jelzik, még mielőtt a szál kilépne a várakozásból, a rendszer ismét hozzárendeli a mutex változót. Ezáltal két dolog válik lehetővé:

- (1) a feltétel bekövetkeztére való várakozás

- (2) a predikátum aktualizálása és az esemény bekövetkeztének a jelzése

A hozzárendelt mutex változó blokkolása

A predikátum beállítása true-ra

Az esemény bekövetkeztének jelzése a feltételes változónak, hogy felélessze a várakozó szálakat

A mutex változó felszabadítása.

  Feltételes változók inicializálása

Egy feltételes változó inicializálása történhet statikus vagy dinamikus módon, a következőképpen:

Posix, statikus inicializálás:

pthread_cond_t FeltValtNeve = PTHREAD_COND_INITIALIZER;

Solaris, statikus inicializálás:

cond_t FeltValtNeve = DEFAULTCV;

A statikus inicializálás tehát egy standard értéknek a feltételes változóhoz történő hozzárendelését feltételezi. A dinamikus inicializálás egy init típusú függvény meghívásával történik, melynek első argumentuma a feltételes változóra mutató pointer.

Posix, dinamikus inicializálás:

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

A pthread_cond_init meghívása a condattr paraméter által megadott attribútumokkal inicializálja a feltételes változót. Ennek a változónak a jelentőségét egy későbbi fejezetben mutatjuk be. Egyelőre tegyük fel, hogy ennek a paraméternek az értéke NULL, vagyis implicit attributumokat rendelünk hozzá a feltételes változóhoz.

Solaris, dinamikus inicializálás:

int cond_init(cond_t *cond, int type, void *arg);

A cond_init függvényhívás type paramétere a feltételes változó láthatósági hatáskörét adja meg. Amennyiben az értéke USYNC_PROCESS, akkor több folyamatból is elérhető. Amennyiben USYNC_THREAD az értéke, a változó csak az aktuális folyamatból érhető el. Az arg paraméter későbbi fejlesztésekre van lefoglalva, ezért egyetlen elfogadott értéke NULL.

  Feltételes változók törlése

Egy feltételes változó törlése során az általa lefoglalva tartott erőforrások fel lesznek szabadítva. A feltételes változóhoz rendelt mutex változónak deblokált állapotban kell lennie. A törlést végző függvények:

Posix

int pthread_cond_destroy(pthread_cond_t *cond);

Solaris

int cond_destroy(cond_t *cond);

  Feltételes változók - várakozás valamilyen eseményre

Posix

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

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

Solaris

int cond_timedwait(cond_t *cond, mutex_t *mutex, timestruc_t *timeout);

A várakozó (wait típusú) függvények meghívása előtt szükség van a cond feltételes változóhoz rendelt mutex változó blokkolására. A wait típusú függvény hívása után a mutex változó fel lesz szabadítva, és a szál végrehajtása felfüggesztődik mindaddig, amíg a várt feltétel teljesül, ebben a pillanatban a mutex változó ismét blokkolva lesz. A timeout paraméter egy maximális várakozási időt határoz meg. Amennyiben a feltétel nem teljesül (az esemény nem következik be) az adott időn belül, ezek a függvények egy hibakódot térítenek vissza.

  Feltételes változók esemény bekövetkeztét jelző műveletek

Posix

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

Solaris

int cond_signal(cond_t *cond);

int cond_broadcast(cond_t *cond);

A signal típusú függvények jelzik a feltétel teljesülését, amelyre a cond változó kapcsán várakoznak. Ha egyetlen szál sincs várakozási állapotban, nem történik semmi. Amennyiben több érdekelt szál van, ezek közül csak egy fogja folytathatni a végrehajtást. Hogy melyik lesz a “felébresztett” szál, ez egyrészt az implementációtól, másrészt a prioritásoktól illetve az ütemezéstől függ. Solaris esetében a megkötött szálak prioritása nagyobb az lwp-khez rendelt szálakénál.

A broadcast típusú függvények a cond változó kapcsán várakozó összes szálat újraindítják.

  Feltételes változó - példa

A továbbiakban egy egyszerű példát mutatunk be. Három szál leírását tartalmazza. Kettő ezek közül - az inccontor függvény írja le mindkettőjük működését - 7-szer növeli a contor változó értékét. A harmadik szál, melyet a watchcontor függvény ír le, azt az eseményt várja, hogy a contor változó értéke elérje a 12-t, és jelzi ezt.

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

int contor = 0;
pthread_mutex_t mutcontor = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condcontor = PTHREAD_COND_INITIALIZER;
int thid[3] = { 0, 1, 2 };
void *incContor (void *tid);
void *verifContor (void *tid);

main ()
{
  pthread_t th[3];
  int i;
  //a 3 szál létrehozása
  pthread_create ((pthread_t *) &th[0], NULL, verifContor, (void *)&thid[0]);
  pthread_create ((pthread_t *) &th[1], NULL, incContor, (void *)&thid[1]);
  pthread_create ((pthread_t *) &th[2], NULL, incContor, (void *)&thid[2]);
  //várakozás a szálak befejezésére
  for (i = 0; i < 3; i++)
    pthread_join (th[i], NULL);
}

void *incContor (void *tid) {
  int *id = (int *) tid;
  int i; printf ("\nSTART incContor %d\n", *id);
  for (i = 0; i < 7; i++) {
    sleep(random() % 3);
    pthread_mutex_lock (&mutcontor);
    contor++;
    printf("\n incContor: thread %d old contor %d new contor %d", *id, contor - 1, contor);
    if (contor == 12)
      pthread_cond_signal (&condcontor);
    pthread_mutex_unlock (&mutcontor);
  }
  printf ("\nSTOP incContor %d\n", *id);
}

void *verifContor (void *tid) {
  int *id = (int *) tid;
  printf ("\nSTART verifContor \n");
  pthread_mutex_lock (&mutcontor);
  while (contor <= 12) {
    pthread_cond_wait (&condcontor, &mutcontor);
    printf ("\n verifContor: thread %d contor %d", *id, contor);
    break;
  }
  pthread_mutex_unlock (&mutcontor);
  printf ("\nSTOP verifContor \n");
}

A program végrehajtásának eredménye a következo:

START verifContor
START incContor 1
START incContor 2

incContor: thread 2 old contor 0 new contor 1
incContor: thread 2 old contor 1 new contor 2
incContor: thread 1 old contor 2 new contor 3
incContor: thread 2 old contor 3 new contor 4
incContor: thread 1 old contor 4 new contor 5
incContor: thread 2 old contor 5 new contor 6
incContor: thread 2 old contor 6 new contor 7
incContor: thread 2 old contor 7 new contor 8
incContor: thread 1 old contor 8 new contor 9
incContor: thread 2 old contor 9 new contor 10
STOP incContor 2

incContor: thread 1 old contor 10 new contor 11
incContor: thread 1 old contor 11 new contor 12

verifContor: thread 0 contor 12
STOP verifContor

incContor: thread 1 old contor 12 new contor 13
incContor: thread 1 old contor 13 new contor 14
STOP incContor 1

  Szemaforokkal végzett műveletek

A folyamatok szintjén értelmezett Unix szemaforok használatával ellentétben a szálakhoz rendelt szemaforok használata egyszerűbb, viszont célszerűbb ugyanazon folyamaton belül használni őket.

  Szemaforok inicializálása:

Posix

int sem_init(sem_t *sem, int type, int v0);

Solaris

int sema_init(sema_t *sem, int v0, int type, void *arg);

Inicializáljuk a sem szemafort v0 értékkel. A type paraméter értéke a Posix-nek megfelelő függvényhívásban 0, ha a szemafor a folyamathoz rendelt lokális szemafor, illetve 0-tól különböző, ha a szemafort több folyamat megosztva használja. A Linux-szálak esetén ez az érték mindig 0.

Ugyanaz a type a Solaris-nak megfelelő függvényhívásban a következő értékeket veheti fel: USYNC_THREAD lokális erőforrás esetén vagy USYNC_PROCESS, amennyiben több folyamat megosztva használhatja. Az arg paraméter értéke kötelezo módon NULL.

Megjegyezzük, hogy Solaris esetében a szemafor statikus inicializálása is lehetséges, kötelező módon a 0 kezdőérték megadásával. Ebben az esetben a szemafor típusa USYNC_THREAD.

  Szemaforok törlése:

Posix

int sem_destroy(sem_t * sem);

Solaris

int sema_destroy(sema_t *sem);

E függvényhívások esetén fel lesznek szabadítva a sem szemafor által foglalt erőforrások. Amennyiben a szemaforhoz nincsenek rendszererőforrások hozzárendelve, a destroy függvények nem tesznek egyebet, mint hogy megvizsgálják, hogy vannak-e szálak, amelyek az illeto szemafornál várakoznak. Ebben az esetben a függvény az EBUSY hibakódot téríti vissza. Nem létező szemafor esetén pedig az EINVAL hibakódot téríti vissza.

  Szemafor értékének növelése (megfelel a V - signal - műveletnek)

Posix

int sem_post(sem_t * sem);

Solaris

int sema_post(sema_t *sem);

A sem_post, illetve sema_post függvények 1-el növelik a sem szemafor értékét. A blokkolt szálak közül az ütemező kiválaszt egyet, és újraindítja. A szál kiválasztása az ütemezési paraméterektől függ.

  Szemafor értékének csökkentése (megfelel a P - wait - műveletnek)

Posix

int sem_wait(sem_t * sem);

int sem_trywait(sem_t * sem);

Solaris

int sem_wait(sema_t *sem);

int sem_trywait(sema_t *sem);

A sem_wait/sema_wait függvények csökkentik a sem szemafor értékét és felfüggesztik az aktuális szál futását, amennyiben az negatívvá vált (atomi utasítás). A sem_trywait/sema_trywait függvények az előbbieknek nem blokkoló verziói. Amennyiben a szemafor értéke nagyobb, mint 0, ennek értéke csökkentve lesz, különben a függvények az EAGAIN hibakóddal fejeződnek be.

A Posix-változatban létezik a következő függvény:

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

mely a sem szemafor aktuális értékét az sval mutató által meghatározott helyre írja be.

  Olvasó/író (reader / writer) típusú blokálás

Ezek az objektumok (melyek mind a Posix mind a Solaris szálak esetén implementálva vannak) arra szolgálnak, hogy megoldják azt, hogy egy adott időben több szál férhessen hozzá egy megosztható erőforráshoz: akárhány szál olvashassa egyszerre, és egyszerre csak egyetlen szál módosíthassa.

  Inicializálás

Posix

int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *rwlockattr);

Solaris

int rwlock_init(rwlock_t *rwlock, int type, void *arg);

Inicializáljuk az rwlock objektumot. A rwlockattr paraméter a Posix változatból az rwlock objektum attribútumainak hordozója. A Solaris-os függvényhívás type paramétere a következő értékeket veheti fel: USYNC_THREAD helyi erőforrás esetén vagy USYNC_PROCESS folyamatok közti megosztott használat esetére. Az arg paraméter értéke kötelező módon NULL.

  A reader/writer objektum törlése

Posix

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

Solaris

int rwlock_destroy(rwlock_t *rwlock);

Az rwlock objektum által lefoglalva tartott erőforrások fel lesznek szabadítva.

  Olvasó blokálás muvelete

Posix

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

Solaris

int rw_rdlock(rwlock_t *rwlock);

int rw_tryrdlock(rwlock_t *rwlock);

Az olvasó blokálás művelete az olvasók számának növelését jelenti, amennyiben egyetlen író folyamat sem blokálta illetve egyetlen író folyamat sem várakozik a reader/writer objektumra. Ellenkező esetben a lock függvények várakozási állapotba juttatják a szálat mindaddig, amíg az objektum felszabadul, a trylock függvények pedig azonnal visszatérnek egy hibakóddal.

  Író blokálás szerepe

Posix

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

Solaris

int rw_wrlock(rwlock_t *rwlock);

int rw_trywrlock(rwlock_t *rwlock);

Az író blokálás szerepe, hogy megszerezze a reader/writer objektumot abban az esetben, amennyiben egyetlen szál sem foglalta le olvasásra vagy írásra. Ellenkező esetben vagy blokálódni fog a hívó szál, amíg az objektum fel nem szabadul a wrlock függvények esetében vagy azonnal visszatér a függvény hibakóddal a wrtrylock függvények esetén.

  Példák szinkronizálásra

A bemutatott szinkronizációs mechanizmusok használatát egy általános szinkronizációs feladat megoldásával példázzuk: m szál próbál n erőforráshoz hozzáférni (m>n) különböző módokon, melyek végül ugyanahhoz az eredményhez vezetnek.

Ennek a helyzetnek a kezelése végett egy konkrét feladatot fogunk megoldani: “nrTr vonat érkezik be egy állomásra nrLin vágányon keresztül nrTr>nrLin”, különböző szinkronizációs eszközök segítségével: szemaforok, mutex változók, stb.

1. Unix alatti implementáció, Posix szemaforok használatával.

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

#define nrLin 5
#define nrTr 13
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
sem_t sem;
int poz[nrTr];
pthread_t tid[nrTr];
void list();
void* pass(void* ssind);

main(int argc, char* argv[])
{
  char* sind;
  int i;
  sem_init(&sem,0,nrLin);
  for (i=0;i < nrTr;i++) {
    sind=(char*) malloc(5*sizeof(char));
    sprintf(sind,"%d",i);
    pthread_create(&tid[i],NULL,pass,(void*)sind);
  }
  for (i=0;i < nrTr;i++)
    pthread_join(tid[i],NULL);
}

//Az állomásra beérkezo vonatok kilistázása
void list()
{
  int i;
  pthread_mutex_lock(&mutex);
  printf("Az allomasra erkezo vonatok:");
  for (i=0;i < nrTr ; i++)
    if (poz[i]==1)
      printf(" %d",i);
  printf("\n");
  pthread_mutex_unlock(&mutex);
}

// a szálat kezelo rutin
void* pass(void* ssind)
{
  char * sind = (char *) ssind;
  int sl,ind;
  ind=atoi((char*)sind);
  sem_wait(&sem);
  poz[ind]=1;
  list();
  sl=1+(int) (3.0*rand()/(RAND_MAX+1.0));
  sleep(sl);
  poz[ind]=2;
  sem_post(&sem);
  free(sind);
}

2. Unix alatti implementálás mutex változók és feltételes változók segítségével.

#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define nrLin 5
#define nrTr 13
pthread_mutex_t semm[nrLin];
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexc=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condElm=PTHREAD_COND_INITIALIZER;
int poz[nrTr];
pthread_t tid[nrTr];
int semmutex_lock();
int semmutex_unlock(int i);
void list();
void* pass(void* ssind);

main(int argc, char* argv[])
{
  char* sind;
  int i;
  for (i=0;i < nrLin;i++)
  //semm[i]=PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_init(&semm[i],NULL);
  for (i=0;i < nrTr;i++) {
    sind=(char*) malloc(5*sizeof(char));
    sprintf(sind,"%d",i);
    pthread_create(&(tid[i]),NULL,pass,(void *)sind);
  }
  for (i=0;i < nrTr;i++)
    pthread_join(tid[i],NULL);
}

int semmutex_lock()
{
  int i;
  while (1) {
    pthread_mutex_lock(&mutexc);
    //megvizsgaljuk, hogy van-e szabad vagany
    for (i=0;i < nrLin;i++){
      if (pthread_mutex_trylock(&semm[i])!=EBUSY){
        pthread_mutex_unlock(&mutexc);
        return i;
   }
  }
  //amennyiben nincs egyetlen szabad vagany sem, blokalodik
  //a condElm felteteles valtozon
  pthread_cond_wait(&condElm,&mutexc);
  pthread_mutex_unlock(&mutexc);
  }
}

int semmutex_unlock(int i)
{
 pthread_mutex_lock(&mutexc);
 //felszabaditjuk az i. vaganyt
 pthread_mutex_unlock(&semm[i]);
 pthread_cond_signal(&condElm);
 pthread_mutex_unlock(&mutexc);
}

//Az állomásra beérkezo vonatok kilistázása
void list()
{
  int i;
  pthread_mutex_lock(&mutex);
  printf("Az allomasra erkezo vonatok:");
  for (i=0;i < nrTr ; i++)
    if (poz[i]==1)
      printf(" %d",i);
  printf("\n");
  pthread_mutex_unlock(&mutex);
}

// a szálat kezelo rutin
void* pass(void* ssind)
{
  char * sind = (char *) ssind;
  int sl,ind,indm;
  ind=atoi((char*)sind);
  indm=semmutex_lock();
  poz[ind]=1;
  list();
  sl=1+(int) (3.0*rand()/(RAND_MAX+1.0));
  sleep(sl);
  poz[ind]=2;
  semmutex_unlock(indm);
  free(sind);
}

Az (1) és (2) program hasonló eredményhez vezet.
Végrehajtás eredménye nrLin=5 és nrTr=13 esetén.

Az allomasra erkezo vonatok: 0
Az allomasra erkezo vonatok: 0 1
Az allomasra erkezo vonatok: 0 1 2
Az allomasra erkezo vonatok: 0 1 2 3
Az allomasra erkezo vonatok: 0 1 2 3 4
Az allomasra erkezo vonatok: 0 2 3 4 5
Az allomasra erkezo vonatok: 5 8
Az allomasra erkezo vonatok: 5 7 8
Az allomasra erkezo vonatok: 5 6 7 8
Az allomasra erkezo vonatok: 5 6 7 8 9
Az allomasra erkezo vonatok: 6 7 8 9 10
Az allomasra erkezo vonatok: 7 8 9 10 11
Az allomasra erkezo vonatok: 7 10 11 12

Észrevehetjük, hogy egy adott pillanatban az állomásra max. 5 vonat érkezik be (ahány vágány van), és a program befejeződik, miután mind a 13 vonat beérkezett az állomásra.

 


Copyright (C) Buzogány László, 2002

About