S-114.240 Laskennallisen tekniikan seminaari

                         Rinnakkaislaskenta: Jaetunmuistin koneet, säikeet

                                                           jouni.juujärvi@hut.fi

                                                                       15.4.1999



Esitelmä perustuu Wilkinson & Allen, Parallel Programming kirjaan, Mika Julkusen erinomaiseen graduun: Prosessit ja Säikeet rinnakkaisohjelmoinnissa, www.numeric-quest.com/lang/multi-frame.html web sivuun säieohjelmoinnista Linuxissa ja BeOS newsletter artikkeleihin.
Esitelmä on jaoteltu seuraavasti: Ensin luodaan nopea silmäys yleisimpään moniprosessori konearkkitehtuuriin, sen jälkeen käydään pikaisesti läpi säie ohjelmoinnin kehittyminen rinnakkaisista prosesseista. Selvitään säie ohjelmointiin liittyviä käsitteitä, käydään läpi säie ohjelmointia Linuxissa ja lopuksi vertaillaan säieohjelmoinnin hyviä ja huonoja puolia.

1. Jaetun muistin koneet

Rinnakkaislaskentajärjestelmät voi karkeasti jakaa kahteen eri luokkaan. Jaetun muistin koneisiin ja viestinvälitystekniikkaan perustuviin monen koneen järjestelmiin. Jaetun muistin koneissa kaikki prosessorit voivat lukea ja kirjoittaa samaan muistiavaruuteen. Halvat ja yleisimmät jaetun muistin koneet perustuvat samaan arkkitehtuuriin kuin yhden prosessorin koneet. Tässä mallissa on yksi väylä jossa on kiini kaikki prosessorit ja muisti (kuva 1).

Kuva 1. PC arkkitehtuuriin perustuva moniprosessori kone
 

Tämä arkkitehtuuri on toimiva ainoastaan kohtuullisen pienille prosessori määrille luokkaa 4-8. Tämä johtuu siitä että väylää pystyy käyttämään vain yksi prosessori kerralla. Prosessorien määrän kasvaessa väylä menee vähitellen tukkoon. Se miten nopeasti tämä tapahtuu riippuu välimuistin määrästä prosessoreissa, ajettavasta ohjelmasta ja väylän kapasiteetista. Intelin alustalla tämä raja saavutetaan  noin 4 prosessorin kohdalla.

PowerPC & Mac puolella ensivuonna ilmestyvät G4 prosessorit ja Maxbus arkkitehtuurissa väylälle mahtuvien prosessorien määrän pitäsi olla huomattavasti suurempi(www.macosrumors.com). Siinä prosessorien välissä on prosessorin kanssa samalla kellotaajuudella toimiva väylä. Sen lisäksi prosessorit jakavat välimuistin keskenään jolloin prosessorien määrän kasvaessa välimuistin määrä kasvaa myös. Tällöin muistiin viittauksien määrä ei kasva yhtä nopeasti kuin perinteisessä tekniikassa.
 

1.2 Ohjelmointi

Rinnakkaisten prosessien luominen on perinteisin tapa ohjelmalle hyödyntää koneen useampaa prosessoria saman aikaisesti Unixissa. Tämä on perinteisesti tapahtunut fork-join komentojen avulla missä prosessista on muodostettu kopio. Äitiprosessi ja lapsiprosessi tunnistavat itsensä fork kutsun paluuarvon avulla ja voivat sen jälkeen ryhtyä suorittamaan omaa osaa ohjelmasta. Molemmat prosessit omistavat oman muistiavaruutensa ja prosessien välinen kommunikointi tapahtuu yleensä putkien ja socketien avulla. Näiden alustus hoidetaan yleensä ennen lapsiprosessin luomista. Prosessien luominen vie paljon CPU aikaa koska siihen liittyy paljon muitakin ominaisuuksia kuin pino ja rekisteriarvot. Samasta syystä myös niiden tuhoaminen ja prosessista toiseen vaihtaminen on aika raskaita operaatioita. Lisäksi niiden välinen kommunikointi on hankalaa ja resursseja kuluttavaa.

Näitä ongelmia helpottamaan ja suoritusta nopeuttamaan kehitettiin säikeet. Saman prosessin sisällä säikeet jakavat saman muistiavaruuden ja kaiken muun paitsi pinon ja prosessorien rekisterien arvon. Koska jokaisella säikeellä on oma pinonsa kaikki dynaamisesti luodut oliot ovat kunkin säikeen omistuksessa. Pinojen sisältöön tosin pääsee toisista säikeistä käsin myös käsiksi jos säie välittää tarvittavat osoitteen pinossa olevaa muuttujaansa. Säikeen prosessien välistä eroa on kuvattu kuvasssa 2. 

Kuva 2. Prosessien ja säikeiden välinen suhde

Säikeen luominen voi olla yli kymmenen kertaa nopeampi toimenpiden kuin prosessin luominen. Uutta säiettä luotaessa saman prosessin sisällä tarvitsee luoda vain uusi pino ja paikka säikeen tarvitsemille prosessorin reikisteri arvoille. Näihin rekisteri arvoihin talletetaan aina prosessorin tila keskeytyksen yhteydessä jotta suoritusta voidaan jatkaa myöhemmin. Myös säikeestä toiseen säikeeseen vaihtamisen pitäisi sujua nopeammin kuin prosessista toiseen. Saman prosessin sisällä ei ytimen tarvitse ajettavan säikeen valitsemisen jälkeen (ja eräiden muiden toimenpiteiden jälkeen) tehdä muuta kuin ladata uudet rekisterit prosessoriin. Tämä nopeus ero ei tosin ei päde Linuxissa jossa säikeet ja prosessit käsitellään samalla schedulerilla ja säilytetään samassa taulussa. Linux schedulerin toteutus on kuitenkin hyvin nopea (ja yksinkertainen) jolloin tästä ei ole kovin suurta haittaa.


2. Säikeet

2.1 Määritelmiä

Silloin kun ohjelma tai kirjasto ei ole suunniteltu käytettäväksi kuin yhden suoritettavan säikeen kanssa kutsutaan yksisäikeiseksi. Tällaista kirjastoa voidaan käyttää monisäikeisestäkin ohjelmasta, mutta silloin pitää huolehtia siitä että vain yksi säie kerrallaan suorittaa kutsuja kirjastoon.

Ne ohjelmat joissa on useampi säikeitä rinnakkain suorituksessa ja kirjastoja joita voi useampi säie suorittaa kutsutaan monisäikeisiksi. Kirjastoja kutsutaan tällöin myös säie turvallisiksi.

Säikeet luodaan usein joko käyttäjätasolla tai ytimessä. Käyttäjätasolla luodut säikeet jakavat tällöin prosessin saaman CPU ajan. Niiden hyötynä on nopeampi vaihtaminen kuin ytimen tason säikeillä, mutta haittana on useamman prosessorin hyödyntämisen menettäminen ja jos joku säie jotuu odottamaan I/O tapahtumaa niin silloin myös muut säikeet jäävät odottamaan. Ytimentason säikeet ovat rinnakkaislaskentaan sopivia koska saman prosessin ytimen tason säikeet voidaan allokoida eri prosessoreille. Käyttäjätason säikeet eivät näy ytimelle joten se ei osaa allokoida niitä eri prosessoreille.

Säieohjelmoinille on IEEEssä määritelty standardi POSIX standardi 1003.1c jota useimmat Unix valmistajat noudattavat. Esim -Sun Solaris 2.5, Digital Unix 4.0, Silicon Graphics Irix6, IBM, HP ja Linux. Standardista tosin osa poikkeaa tai ei ole toteuttanut sitä ihan kokonaan, mutta ohjelmat on kuitenkin hyvin siirrettävissä alustalta toiselle rajapinnan perusteella.

Linux säiekirjastoja on useampia joista suurinosa toimii käyttäjätason säikeillä. Tässä esitemässä kuitenkin keskitytään kirjastoon nimeltä LinuxThreads mikä toimii ytimentason säikeillä ja näin ollen soveltuu monen prosessorin samanaikaiseen hyödyntämiseen.

2.2 Ohjelmointi Linuxissa (Posix 1003.1c:ssä)

2.2.1 Luominen

Mikä tahansa säie voi luoda uuden säikeen funktio kutsulla pthread_create. Tällöin uusi säie aloittaa suorittamaa kutsussa määriteltyä funktiota rinnakkaisesti aikaisemnnin  määritellyn funktion kanssa.

Uutta säiettä luotaessa sille annetaan parametrina thread attirbute. Se voi olla vasta luotu tai sitten samaa oliota voi käyttää useamman kerran. tällöin täytyy huomata että kutusun jälkeen tehdyt muutokset ei vaikuta jo luotuun säikeeseen. Jos funktiokutsussa annetaan NULL pointteri olion sijasta niin silloin säie luodaan oletusarvoilla.

Säikeellä on seuraavat ominaisuudet

2.2.2 Schedulointi

Schedulointi on ytimen osa joka päättää mikä ajettavissa oleva säie/prosessi suoritetaan CPUssa seuraavaksi. Linuxissa sama scheduler käsittelee sekä säikeet että prosessit.

Scheduloija tarjoaa kolmea erillaista schedulointi tapaa, yhtä tavallisille prosesseille ja kahta muuta reaaliaikaisille prosesseille. Pysyvä prioriteetti arvo sched_priority on annettuna kullekkin prosessille ja tätä arvoa voi muuttaa vain systeemikutsulla. Tämä arvo on luku välillä 0 - 99. Scheduloija valitsee ajovalmiista prosesseista suurimman luvun aina seuraavaksi ajettavaksi. Schedulointi tapa vaikuttaa siihen miten uusi ajettava prosessi sijoitetaan samanarvoisten prosessien listalla ja miten sitä liikutellaan tämän listan sisällä.

SCHED_OTHER on oletus tapa  jota suurin osa prosesseista käyttää. Tällöin prioriteetti on asetettuna 0:n. SCHED_FIFO ja SCHED_RR on tarkoitettu vain reaaalikaisille prosesseille ja niiden proriteeti arvot on välillä 1-  99. Vain ne prosessit joita ajetaan superkäyttäjän oikeuksilla  voivat saada reaaliaika prosessin arvoja.

Linuxissa schedulointi parametreja voi lukea ja kirjoittaa funktio kutsuilla pthread_setschedparam ja pthread_getschedparam.

Kannattaa myös huomioida että Posix standardi ei takaa mitään säikeiden suorituksen reiluudelle ja ei myöskään Linux joka voi sopivissa olosuhteissa selvästi suosia joitain säikeitä toisten kustannuksella.

2.2.3 Lopettaminen

Säikeiden ajaminen voidaan lopettaa usealla tavalla:

2.2.3.1 Exit

Säie lopettaa oman toimintansa kutsumalla pthread_exit funktiota. Tällöin kaikki säikeelle määritetyt siivous funktiot (pthread_cleanup_push) suoritetaan käänteisessä järjestyksesta aloittaen viimeiseksi talletetusta. Sen jälkeen suoritetaan lopeutus funktion ei NULL arvoisille data alkioille jotka on luotu kutsulla pthread_create. Lopuksi säikeen suoritus lopetetaan.

2.2.3.2 Cancellation

Mikä tahansa säie voi lähettää keskeytys pyynnön toiselle säikeella jos se haluaa lopettaa toisen säikeen samalla tavalla kuin keskeytettävästä olisi kutsuttu pthread_exit(PTHREAD_CANCELED) fuktiota.

Keskeytettävä säie voi olla huomioimatta pyyntöä, toteuttaa sen heti tai vasta kun se saavuttaa keskeytyspaikan riippuen säikeen asetuksista:

Keskeytyspisteet  on paikkoja jossa tarkistetaan onko keskeytyspyyntöä lähetetty säikeelle ja jos on niin se suoritetaan . Seuraavat  funktiot toimivat keskeytyspaikkoina Posix:ssa & Linuxissa: Mikään muun säikeisiin liittyvä funktiokutsu ei Posixissa voi olla keskeytys paikkana, mutta systeemikutsut ja niitä puolestaan kutsuvat funktiokutsut voivat esim read, write, wait, fprintf. Näistä mikään ei toimi keskeytyspaikkana toistaiseksi Linux:ssa koska ydin ei tue tämän ominaisuuden toteuttamista.

2.2.3 Siivoaminen

Siivouskahvat on osittimia funktioihin joita kutsutaan siinä vaiheessa kun säikeen suoritusta ollaan keskeyttämässä joko kutsumalla pthread_exit tai pthread_cancel funktiolla. Siivousfunktioita kutsutaan käänteisessä järjestyksessä eli viimeksi asetettu kutsutaan ensin.

Siivouskahvojen tarkoituksena on vapauttaa ne resurssit joita säikeellä on sillä hetkellä kun sitä ollaan keskeyttämässä. Esim jos säie lopetetaan siinä vaiheessa kun sillä on mutex lukittuna se jää pysyvästi lukkoon. Tämä voidaan välttää parhaiten asettamalla siivouskahvaksi pthread_mutex_unlock joka vapauttaa lukon kutsuttaessa.

Siivouskahvoja asetetaan ja poistetaan kutsuilla pthread_cleanup_push ja pthread_cleanup_pop. Nämä kutsut pitäisi aina esiintyä pareina samalla tavalla kuin muistinvaraamis ja -vapauttamis kutsut. Linuxissa tämä voidaan toteuttaa makrona jossa aaltosulun alkuun tuleen push ja loppuun pop.

Linuxista löytyy myös Posix standardista poikkeavasti seuraavat makrot:

2.2.4 Synkronisointi

Koska säikeet jakavat paljon resursseja saman prosessin sisällä niiden täytyy toimia yhteistyössä toistensa kanssa.

Tämä on ongelma silloin kun useampi säie samanaikaisesti käsittelee jotain oliota muuttaen sen tilaa. Tällöin toiset säikeet voivat muuttaa olion tilaa kesken ensimmäisen säikeen suorittamista.

On tietenkin mahdollista että säikeet eivät käytä mitään yhteisiä resursseja päällekkäin jolloin synkronisointia ei tarvita. Tämä ei kuintenkaan toteudu usein vaan yleensä joitan resursseja pitää jakaa. Synkronisointitapoja on useita joita on selitettynä tarkemmin alla.

Liittäminen

Säie voi lopettaa toimintansa kunnes toinen säie on lopettanut suorituksensa. Tämä tehdään funktiokutsulla pthread_join. Jos toinen säie on määritelty liitettäväksi se vapauttaa resurssinsa vasta tämän kutsun jälkeen.

Mutex

Mutex on lukko joka estää kahta säiettä pääsemästä kriittiseen kohtaan samanaikaisesti. Sillä on kaksi tilaa. Lukitsematon (jollon kukaan säie ei omista sitä) ja lukittu. Vain yksi säie kerrallaan pystyy lukitsemaan sen. Säie joka yrittää saada lukkoa joka on jo lukittuna jää odottamaan kunnes lukko avataan sen varanneen säikeen toimesta. Lukko pitää alustaa ennen kuin se voidaan ottaa käyttöön ja useapi säie ei saa alustaa sitä samanaikaisesti. Alustusta ei myöskään saa suorittaa kun se on käytössä toisen säikeen toimesta.

Linuxissa on käytössä kahdenlaisia Mutexeja.

Rekursiivisesti kutsuttavissa olevaa mutexia sama säie voi yrittää lukita uudelleen jäämättä jumiin.

Mutexsia voi yrittää lukita kutsulla pthread_mutex_lock ja sen voi vapauttaa kutsulla pthread_mutex_unlock. Rekursiivisessa tapauksessa säikeen pitää vapauttaa lukko yhtämonta kertaa kuin se on lukittuna. Säikeen lukitsemista voi yrittää myös kutsulla pthread_mutex_trylock. Tämä kutsu ei jää odottamaan jos säie on varattuna vaan kertoo lukituksen onnistumisen paluuarvona.

    Ehdot

Ehtojen avulla voi joku säie pysäyttää toimintansa kunnes se uudelleen käynnistetään. Operaatiot voi jakaan kahteen osaan: Yllämainittuja funktioita pitää käyttää lukkojen avulla välttääkseen tilanteita joissa samaan aikaan kun ensimmäinen säie valmistautuu odottamaan ehdon toteuttamista toinen säie laukaisee ehdon jättäen ensimmäisen säikeen odottamaan.

Kannattaa myös huomioida että jos vain yksi säie herätetään ja odottamassa on useampia säikeitä niin ei ole mitään määrättyä järjestystä säikeen valinnassa. Ehto muuttujaa ei saa useampi säie alustaa samanaikaisesti ja sitä ei myöskään luonnollisesti saa alustaa samalla kun joku säie jo käyttää sitä. Alustus tehdään funktiolla pthread_cond_init. Se saa argumentiksi Posix standardissa määritellyn cond_attr mutta tällä argumentilla ei ole mitään merkitystä Linux toteutuksessa. Ehto muuttuja tuhotaan komennolla pthread_cond_destroy. Tällöin ei tietenkään mikään säie ei saa enään olla odottamassa ehdon toteutumista. Linux toteutuskessa tosin ehtomuuttuja ei varaa mitään resursseja joten sen tuhoaminen ei tee mitään muuta kuin tarkistaa ettei mikään säie ole odottamassa sen toteutumista.

Readers/Writers Locks

Tämä käsite toimii vain Solaris-ympäristössä. Se on hitaampi kuin mutex mutta toimii paremmin usean lukijan tapauksessa. Tällöin se antaa monen lukijan päästä käsiksi dataan jos kirjoittaja ei ole varannut sitä itselleen.

    Semaphorit

Semaphorit on hyvin samankaisia mutex:n kanssa. Niitä on kahdenlaisia. Binary semaphorit eroavat mutexista vain siinä että semaphorin vapauttava säie voi olla eri kuin sen varannut. Counting semaphorin avulla voidaan tehdä samoja asioita kuin ehtojen avulla mutta monesti helpommin. Tosin pitää huomioida että mutexin ja ehtojen avulla suojattu osa näkyy aaltosulkujen sisällä jolloin rakenne on helppo hahmottaa. Semaphoreilla rakenne ei näy niin selvästi ja niitä pystyy muutenkin käyttämään hyvin kontrolloimattomalla tavalla josta on vaikea ottaa selvää. Semaphoreja käytetään yleensä kun jotain resursseja on rajallinen määrä jolloin jokainen varaus vähentää arvoa(semaphorin==vapaiden resurssien määrä) yhdellä ja lopulta se säie joka yrittää allokoida nollasta jää odottamaan semaphorin vapautumista. Koska semaphorit on monimutkaisempia kuin mutex niin ne on myös vähän raskaampia operaatioita.

    Benaphorit

BeOS käyttöjärjestelmässä on keksitty uusi tapa keventää binäärisemaphorien käyttöä. Se vaatii käyttöjärjestelmältä tukea sen verran että järjestelmän on tuettava  atomista lisäys ja arvon tarkistusfunktiota. Tämän funktion toteutus on huomattavasti kevyempi kuin semaphorin varaamisen tarkistaminen. Toiminta idea on varata semaphori etukäteen ja vain kilpailutilanteessa käyttää varattua semaphoria. Alla toimintaa valaiseva koodi esimerkki:

while(true){
  if(atomic_add(&g_lock,1)>0)
    accuire_sem(g_ben);
  do_critical_section();
   if(atomic_add(&g_lock,-1)>1)
    release_sem(g_ben);
}

BeOSssä atomic_add kestää 1/10 semaphorin tarkistus ajasta joten jos usein toistuvassa varaustilanteessa jossa ei läheskään aina ole kilpailutilanne ajan säästö voi olla huomattava. Koska Linux on vapaasti koodattavissa on siinä mahdollista kirjoittaa myös atomic_add funktio ja käyttää sen avulla myös benaphoreja.

Monitorit

Javassa on pyritty helpottamaan rinnakkaisten säikeiden käyttöä. Siinä suojataan tarvittava koodin osa kirjoittamalla funktion esittelyssä avain sana syncronized. Tällöin vain yksi säie kerrallaan pääsee suorittamaan funktiota. Monitoreja on myös käytössä joissain muissa järkestelmissä mutta ne eivät ainakaan toistaiseksi kuulu Posix 1003 standardiin eikä myöskään Linux kirjastoon. Ne ilmeisesti kuuluvat ohjelmointi kielen tasolle joten jos niitä haluttaisiin käyttää ne pitäisi sisällyttää suoraan C++:n ja kääntäjiin.

2.2.5 Datan välitys & erikoisfunktiot

Koska säikeet jakavat saman muistiavaruuden niiden välinen datan välitys ei ole ongelma. Jos säikeet haluavat muuttujia joilla on eri arvot eri säikeillä ne luodaan erikoisfunktioilla. Funktiot ovat seuraavat:

Once

pthread_once funktio saa argumentteina funktio ja once_control muuttuja. Funktio kutsuu saatua funktiota jos samalla once_control muuttujalla ei pthread_once funktiota ole kutusuttu aikaisemmin. Tämän kutsun tarkoituksena on helpottaa sellaisten muuttujien alustamista mitkä on tarkoitettu alustettavaksi vain kerran.

2.2.6 Signaalit

Kekeytykset on tietokoneen laitetason tapahtumia jotka vaativat prosessoria suorittamaan jonkun ennalta määrätyn keskeytys rutiinin. Rutiinin suoritettuaan prosessori palaa suorittamaan keskenjäänyttä ohjelmaa. Signaalit on keskeytysten vastineita ohjelma tasolla. Ne ovat ohjelmatasolla syntyviä tapahtumia, jotka saavat vastaanottavan prosessin suorittamaan käsittelijän signaalille ja suorituksen päätyttyä palaamaan takaisin keskeytyneen prosessin suorittamiseen. Koska säikeet jo antavat mahdollisuuden asynkroniseen toimintaan niin säie ohjelmassa kannattaa luoda yksi säie sopivalla maskilla jonka tehtävänä on käsitellä kaikki halutut signaalit. Tällöin säikeessa kutsutaan sigwait funktiota ja jäädään odottamaan niiden saapumista. Mitään erillisiä käsittelijä funktioita ei kannata käyttää.

    Signaalikäsittelijät

Näitä on olemassa vain yksi signaalityyppi/prosessi. Näiden käyttöä ei suositella.

    Maskit

Maskien avulla valitaan mitä signaaleja säie suostuu ottamaan vastaan jos samalla prosessilla on useita säikeitä jotka suostuvat käsittelemään signaalin niin silloin joku niistä saa signaalin suoritettavakseen. Kuten yllä on mainittuna kannattaa maskata kakki signaalit muilta paitsi signaalin käsittelijä säikeelta.

    Turvallisuus

Kaikkia posix punktioita ei ole lupa kutsua signaalikäsittelijästä käsin tämä johtuu siitä että funktion suoritus saattoi olla kesken kun käsittelijää kutsuttiin. Tämän takia signaalikäsittelijässä toiminta kannattaa minimoida lähinnä jonkun flagin arvon muuttamiseksi tai/ja viestien lähettämiseksi muille säikeille.

    SIGUSR1 & 2

Nämä signaalit on varattuna Linuxkirjaston toteutukseen joten niitä ei voi käyttää.

2.3 Suden kuopat ja muistettavaa

Deadlock

Deadlock syntyy kun säikeen toiminta estyy pysyvästi jonkun resurssin odottamisen takia. Syitä tälläiseen on monia mutta yleisin on lukon uudelleen lukitseminen saman funktion sisällä. Tällöin yleensä funktio on jo lukinnut lukon ja kutsuu jotain toista funktiota joka joko suoraan tai kutsumalla jotain kolmatta funktiota yrittää lukita samaa lukkoa uudelleen. Ratkaisuna on välttää olion ulkopuolelle suoritettavia kutsuja ja jos niitä tarvitseen niin tarvittaessa vapauttaa lukko kutsun ajaksi ja lukita sitten uudelleen kun kutsu palaa.

Toinen hyvin yleinen deadlock tilanne syntyy siitä että säikeet odottavat toisiltaan resurssien vapauttamista. Tähän on ratkaisuna resurssien varaaminen aina samassa järjestyksessä. Jos tähän sääntöön pitää tehdä poikkeuksia niin silloin pitää käyttää pthread_mutex_trylock:ia ja huomioida varaussäännön rikkominen.

Nälkiintyminen

Koska Linux (ja monet muutkin) scheduleri on toteutettu nopeana ja hyvin yksinkertaisena palveluna se ei takaa mitään reiluudesta. Tämän takia voi joku säie jäädä melkein aina odottamaan kun joku toinen säie saa pitää lukkoa hallussaan hyvin pitkiä aikoja. Tällöin yleensä lukkoa hallussa pitävä säie kyllä vapauttaa lukon mutta ehtii varata sen melkein aina uudelleen ennen kuin kukaan muu ehtii varata sen. Ongelma johtuu huonosti suunnitellusta ohjelmasta ja jos tarvitaan takeita reiluudesta eri säikeiden välillä niin silloin niitä pitää ajaa reaaliaika secdulointi määrityksillä (round-robin tai FIFO).

Globaalit muuttujat

Yleensä globaaleja muuttujia ei ole suunniteltu säie turvallisiksi. Esim ERRNO muuttuja osoittaa viimeksi tapahtuneen virheen syytä. Säie ohjelmassa sen arvon voi muuttaa joku toinen säie ennen kuin aikaisemman virheen luonut säie ehtii lukemaan arvon talteen. Tämä ongelma on korjattu Linux Threads paketissa tekemällä ERRNO:sta säikeen sisäinen muuttuja.

Staattinen data

Jotkut ei säie turvalliset funktiot palauttavat osoittimia staattisiin muuttujiin. Näiden arvoa voi toinen säie muuttaa ennen kuin ensimmäinen lukee sen talteen. Tämä ongelma voidaan ratkaista mutexilla estämällä muut kutsut kirjastoon/funktioon ennen arvon lukemista. Tai sitten voidaan käyttää säie ystävällisiä versioista jotka yleensä ovat _r loppuisia.

Lukitsemisesta

Lukitseminen tapahtuu joko funktio tai data tasolla. Funktio tasolla lukkoa pidetään koko funktion suorituksen ajan. Data tasolla sitä pitdetään vain sen aikaa kun yhteistä muuttujaa/resurssia käsitellään. Data tason lukitseminen on hienonpi tasoista ja rinnakkaisuuden puolesta suositeltavampaa, mutta toisaalta yleinen viisaus on aloittaa ensin karkeampitasoisilla lukoilla tarkastella suorituskyvyn perusteella missä tarvitaan hienojakoisempia lukkoja.

Muita yleisiä nyrkkisääntöjä on:

2.4 Esimerkkejä

Seuraava esimerkki laskeen summan 1:stä n:ään.
int a[array_size];
int gloabal_index=0;
int sum=0;
pthread_mutex_t mutex1;

void *slave(void *ignored){
  int local_index, partial_sum=0;
  do {
    pthread_mutex_lock(&mutex1); //Luetaan seuraava alkio listasta ja lisätään se paikalliseen summaan
    local_index=global_index;
    global_index++;
    pthread_mutex_unlock(&mutex1);
    if(local_index < array_size)
      partial_sum+= *(a+ local_index);
  }while (local_index<array_size)
  pthread_mutex_lock(&mutex1);
  sum +=partial_sum;
  pthread_mutex_unlock(&mutex1);
  return(); //Säie lopettaa toimintansa
}

main(){
  int i;
  pthread_t thread[10];
  pthread_mutex_init(&mutex1,NULL); //Alustetaan mutex
  for(i=0;i<array_size;i++) //Talletetaan arvot a:han
  a[i]=i+1;
  for(i=0;i<no_prosesses;i++) //Luodaan työn tekevät säikeet
    if(pthread_create(&thread[i],NULL,slave,NULL)=!0)
      perror("Pthread_create fails");
  for(i=0;i<no_prosesses;i++) //Odotetaan kunnes kaikki säikeet on päässeet loppuun
    if(pthread_join[i],NULL)!=0)
      perror("Pthread_join fails");
  printf("The sum of 1 to %i is %d\n",array_size,sum);
}

Lisää esimerkkejä löytyy Linkkilistassa mainitusta www.numeric-quest.comin linkistä.


3 Summa summarum

3.1 Säie ohjelmoinnin hyödyt ja haitat

3.2 Jaetun muistin koneet & säikeet /  hajautetun muistin koneet & sanomien lähettäminen

Se että kannattaako käyttää jaetun muistin koneita ja säikeitä vaiko hajautetun muistin koneita ja sanomja kirjastoa riippuu aika pitkälle ongelman luonteesta. Tällä hetkellä halpoja jaetunmuistin koneita saa vain aika pienellä prosessorimäärällä varustettuna.

2 prosesorinen PC tulee halvemmaksi kuin 2 erillisen koneen osto ja niiden muodostaman verkon luominen. 4 prosessorinen PC ja 4 erillisen PC:n hinnat liikkuvat samoissa. Jos taas tarvitaan enemmän laskenta tehoa niin siinä vaiheessa pitänee käyttää jotain muuta kuin PC alustaa ja hinnat nousevat jyrkästi.

Toisaalta jos ajatellaan säikeiden välistä kommunikointinopeutta eri prosessorien välissä niin se on teoriassa sama kuin väylänopeus joka on PCssä tällä hetkellä 100MHz x 32 bittiä. Beowulfklusteri joka on kytketty 100Mhz Ethernet korteilla saavuttaa maksimissaan (Ethrenet verkko saturoituu noin 20% kohdalla) 20MHz x 1bitin siirtonopeuden. Sen siirtokapasiteetti on siis alle 1/150 osa prosessorien välisestä kommunikointinopeudesta ja sen lisäksi kommunkointi nopeutta huonotavat kaikki normaalit lähiverkkoon liittyvät viiveet ja epämääräisyydet.

Mikään ei tietenkään estä käyttämästä molempia tapoja sekaisin. Toisaalta tulevaisuus riippuu varmasti voimakkaasti myös siitä mihin suuntaan prosessoriteknologia tulevaisuudessa suuntautuu. Tilanne muuttuu ainakin siinä tapauksessa selvästi jos monen prosessorin tekeminen samalla piipalalle yleistyy ja moniprosessori emolevyt yleistyvät. Tällähetkellä PC puolella taitaa kyseessä olla 'muna kana' ongelma. Harva ohjelma on suunniteltu ajejttavaksi moniprosessori koneessa, jolloin moniprosessori koneillakaan ei ole suurta kysyntää.  Toisaalta verkkoteknologia menee myös kokoajan nopeasti eteenpäin  joten tulevaisuudesta on vaikea ennustaa mitään varmaa.


4. Liitteet

4.1 Linkit


Barry Wilkinson, Michael Allen, Parallel Programming : Techniques and Applications Using Networked Workstations and Parallel Computers, Prentice Hall, 1998

Mika Julkunen, Prosessit ja Säikeet Rinnakkaisohjelmoinnnissa Pro Gradu -tutkijelma 12.2.1997 Helsingin Yliopisto.

www.be.com
www.numeric-quest.com/lang/multi-frame.html