812315A Ohjelmiston rakentaminen. Asynkronisuus Ari Vesanen ari.vesanen (at) oulu.fi
Yleistä moduulista Tällä kertaa sisältää Java-kielistä monisäieohjelmointia Suoritustapa: Neljästä ohjelmointitehtävästä valitaan kaksi, joihin laaditaan ratkaisut Arvostellaan 1-5, loppuarvosana keskiarvo mahdollisesti ylöspäin pyöristettynä Suositellaan tekemään Javalla, C++ mahdollinen, muista kielistä sovittava erikseen 812315 Ohjelmiston rakentaminen, Asynkronisuus 2
Moduulin aikataulu Aloitusseminaari 16.6. klo 10-16 Lyhyt johdanto rinnakkaisuuteen ja Java-kielen monisäieohjelmointiin Rinnakkaisuuden piirteitä ja ohjelmointitehtäviä Suoritettavien tehtävien läpikäynti 17.6. 4.8. Kiivasta ohjelmointia ja tehtävien palautus viimeistään 4.8 Jokainen palauttaa omat ratkaisut Lopetusseminaari 5.8. klo 10-16 Käydään läpi ratkaisut ja vertaillaan niitä toisiinsa 812315 Ohjelmiston rakentaminen, Asynkronisuus 3
Moduulin sisältö I. Johdanto rinnakkaisuuteen II. Javan rinnakkaisuuden perustoteutus III. Rinnakkaisen ohjelmoinnin malleja IV. Eloisuusongelmista V. Edistyneempi Javan rinnakkaisuus VI. Tehtävät 812315 Ohjelmiston rakentaminen, Asynkronisuus 4
Kirjallisuutta Goetz, B. et al.: Java Concurrency in Practice, Addison-Wesley 2010 González, J.: Java 7 Concurrency Cookbook. Packt Publishing 2012. Hartley, S.: Concurrent Programming, The Java Programming Language, Oxford University Press Inc. 1998 Lea, D.: Concurrent Programming in Java, design Principles and Patterns Second Edition, Addison-Wesley 2000 Kaksi viimeistä ennen Java 5.0:aa 812315 Ohjelmiston rakentaminen, Asynkronisuus 5
WWW-lähteitä Javan API-dokumentaatio: https://docs.oracle.com/javase/8/docs/api/ Oraclen Java-tutoriaali http://docs.oracle.com/javase/tutorial/essential/concurrency Muita Java-tutoriaaleja http://www.tutorialspoint.com/java/java_multithreading.htm http://tutorials.jenkov.com/java-concurrency/index.html Aalto-yliopiston kurssi https://noppa.aalto.fi/noppa/kurssi/t-106.5600/luennot 812315 Ohjelmiston rakentaminen, Asynkronisuus 6
I Johdanto rinnakkaisuuteen Peräkkäinen vs. Rinnakkainen Käskyt suoritetaan peräkkäin Yksikäsitteinen suorituspolku Deterministinen sama syöte, sama tulos aina Toimintoja suoritetaan rinnakkain Ei selvää suorituspolkua Epädeterministinen tulos voi riippua suoritusjärjestyksestä 812315 Ohjelmiston rakentaminen, Asynkronisuus 7
I.1 Prosessit ja säikeet Prosessi Säie Tapa ajaa useita ohjelmia rinnakkain yhdessä prosessorissa Oma muistialue Kommunikointi esim. putkilla tai socketeilla Prosessia kevyempi Prosessi voidaan jakaa säikeisiin Säikeellä oma ohjelmalaskuri ja pino Säikeet jakavat prosessin muistialueen ja resurssit Tässä moduulissa monisäieohjelmointia 812315 Ohjelmiston rakentaminen, Asynkronisuus 8
I.2 Rinnakkaisen ohjelman oikeellisuuskriteerit Turvallisuus (safety) Oliot ja muuttujat pysyvät kunnossa Eloisuus (liveness) Kaikki aiotut operaatiot suoritetaan joskus Turvallisuus: Ei-atomaarinen operaatio voi epäonnistua jos kaksi säiettä toimii yhtä aikaa -> Metodi voi toimia väärin jos sitä kutsutaan kahdesta säikeestä Säieturvallinen (thread safe): toimii oikein millä tahansa kutsujärjestyksellä Resurssin käsitteleminen kahdesta säikeestä samanaikaisesti = kilpailutilanne (race condition) 812315 Ohjelmiston rakentaminen, Asynkronisuus 9
I.2 Rinnakkaisen ohjelman oikeellisuuskriteerit Eloisuus: Aiotut operaatiot suoritetaan ei aikarajaa Reaaliaikainen ohjelmointi asetetaan aikarajoja Turvallisuus ja eloisuus jossain määrin vastakkaisia Ongelmatilanteisiin varauduttava suunnitteluvaiheessa Ohjelman debuggaus erittäin vaikeaa (lokitiedoston kirjoitus usein auttaa) Ohjelman täydellinen testaus mahdotonta 812315 Ohjelmiston rakentaminen, Asynkronisuus 10
II Javan rinnakkaisuuden perustoteutus Java alkujaan suunniteltu tukemaan rinnakkaisuutta Olio-ohjelmoinnin ja Javan perusteet oletetaan tunnetuksi Javan virtuaalikone (JVM) suorittaa Java-ohjelman huolehtii myös rinnakkaisuudesta 812315 Ohjelmiston rakentaminen, Asynkronisuus 11
II.1. Javan säikeet Käyttäjäsäikeet (user threads) varsinaiset säikeet Taustasäikeet (daemon threads) toimivat taustalla Ohjelma loppuu, kun sen kaikki käyttäjäsäikeet päättyvät tai kutsutaan Runtime luokan exit()-metodia (koodissa System.exit(0);) 812315 Ohjelmiston rakentaminen, Asynkronisuus 12
II.1. Javan säikeet. Säikeen tilat Uusi (NEW) Säie on luotu, ei voi vielä toimia Ajettava (RUNNABLE) Säie voi toimia, kun saa suoritusaikaa Estetty (BLOCKED) Toiminta on estetty, koska odottaa lukon vapautumista Odottaa (WAITING) Säie odottaa toisen säikeen operaatiota Odottaa ajastetusti (TIMED_WAITING) Kuten yllä tai kun annettu aika on kulunut Lopetettu, kuollut (TERMINATED) Säikeen suoritus on loppunut 812315 Ohjelmiston rakentaminen, Asynkronisuus 13
II.1. Javan säikeet. Säikeen luominen Kirjoita luokka, joka perii Thread-luokan run()-metodi uudelleenmääriteltävä TAI Kirjoita luokka joka toteuttaa Runnable rajapinnan toteutettava run()-metodi Säikeen suorittaminen: 1. Luo uusi Thread-olio 2. Konfiguroi (ei pakollinen) Anna prioriteetti ja nimi 3. Käynnistä kutsumalla start()-metodia 812315 Ohjelmiston rakentaminen, Asynkronisuus 14
II.1. Javan säikeet. Säikeen luominen (2) HUOM1: Käynnistys kutsumalla startia, toiminnallisuus runissa! HUOM2: Säie päättyy, kun run loppuu Testaa ohjelmia Basicthreads.java BasicRunnable.java 812315 Ohjelmiston rakentaminen, Asynkronisuus 15
II.1. Javan säikeet. Säikeen lopettaminen Säie päättyy kun run loppuu Suositeltavin tapa säikeen keskeyttämiseen: interrupt asettaa säikeen keskeytystilaan, ei lopeta suoritusta tutki onko keskeytetty (metodi isinterrupted()) myös interrupted() - nollaa keskytystilan Nukkuvan tai odottavan säikeen keskeyttäminen aiheuttaa poikkeuksen InterruptedException säie ei ajossa -> ei voi suoraan keskeyttää ko. poikkeus on aina käsiteltävä kun tehdään wait tai sleep 812315 Ohjelmiston rakentaminen, Asynkronisuus 16
II.1. Javan säikeet. Säikeen lopettaminen //Määritellään säieluokka class Interruptible extends Thread{ public void run(){ while(!this.isinterrupted()){ // Tee jotain} // Lopputoimenpiteet } } //Pääohjelmassa koodi: Interruptible irthread = new Interruptible(); irthread.start(); // Toimenpiteitä pääohjelmassa // Lopetetaan irthread irthread.interrupt(); Tehtävä ThreadInterrupt.java 812315 Ohjelmiston rakentaminen, Asynkronisuus 17
II.2. Javan säikeiden synkronointi Oikean toiminnan varmistamiseksi kahdenlaista synkronointia 1. Kilpailun synkronointi Tarvitaan kun resurssia käytetään monesta säikeestä 2. Yhteistoiminnan synkronointi Tarvitaan kun säikeen toiminta riippuu toisen säikeen toiminnasta Javan perusmekanismi monitori = olio, jolla on lukko ja odotusjoukko Mikä tahansa Javan olio voi olla monitori 812315 Ohjelmiston rakentaminen, Asynkronisuus 18
II.2.1 Javan monitorin toiminta P Q SÄIE 1 SP. JOUKKO SÄIE 5 SÄIE 6 METODI1 Odotusjoukko (wait set) = ne säikeet jotka odottavat ilmoitusta (notify()) kutsuttuaan wait() metodia SÄIE 3 SÄIE 2 SÄIE 4 ODOTUSJOUKKO LOHKO1 METODI2 Sisäänpääsyjoukko (entry set) = säikeet, jotka odottavat pääsyä monitoriin ensimmäistä kertaa 812315 Ohjelmiston rakentaminen, Asynkronisuus 19
II.2.2 Kilpailun synkronointi Lukko otetaan haltuun kutsumalla synchronizedavainsanalla merkittyä metodia (tai koodilohkoa) olion kaikkien synkronoitujen metodien ja lohkojen suorittaminen estyy kunnes säie luopuu lukosta Lukosta luopuminen: Synkronoitu metodi/lohko loppuu Säie siirtyy odotustilaan HUOM! Thread.sleep(); ei aiheuta lukosta luopumista. 812315 Ohjelmiston rakentaminen, Asynkronisuus 20
II.2.2 Kilpailun synkronointi (2) P Q ESIM: Seuraavat yhtäpitävät: synchronized void f(){ // Metodin runko } void f(){ synchronized(this){ // Metodin runko } } Tehtävä: Poista kilpailutilanne ohjelmista RaceCondition.java ja UnsafeTicketOffice.java Optimoi synkronointi jälkimmäisessä 812315 Ohjelmiston rakentaminen, Asynkronisuus 21
II.2.3 Yhteistoiminnan synkronointi Säikeessä voidaan odottaa toisen säikeen päättymistä kutsumalla sen join()-metodia Monitorin odotusjoukkoon voidaan vaikuttaa metodeilla wait(): asettaa kutsuvan säikeen odotustilaan notify(): vapauttaa yhden odottavan säikeen notifyall(): vapauttaa kaikki odottavat säikeet Kaikkien em. metodien kutsumiseksi on oltava hallussa monitorin lukko Vapautetut säikeet kilpailevat normaaliin tapaan monitorin lukosta 812315 Ohjelmiston rakentaminen, Asynkronisuus 22
II.2.3 Yhteistoiminnan synkronointi (2) Jos odottava säie keskeytetään, syntyy InterruptedException Käsiteltävä, jos kutsutaan wait()-metodia Tehtävä: Ohjelmassa PingPongEx.java kaksi säiettä kutsuu luokan PingPongController metodeja, jotka tulostavat sanat PING ja PONG Synkronoi luokka PingPongController niin, että sanat tulostetaan aina vuorotellen. Metodeissa esiintyviä sleep-aikoja ei saa muuttaa 812315 Ohjelmiston rakentaminen, Asynkronisuus 23
III Rinnakkaisen ohjelmoinnin malleja III.1 Tuottaja-kuluttajamalli P Q Soveltuu moneen rinnakkaisen ohjelmoinnin ongelmaan Kahdentyyppisiä säikeitä (tai prosesseja): Tuottajat Luovat uusia olioita (dataa) Kuluttaja Käsittelevät tuottajien luomia olioita Olioiden varastona voidaan käyttää esim. synkronoitua syklistä puskuria: Huolehdittava, että vain yksi säie käsittelee puskuria Huolehdittava että täyteen puskuriin ei kirjoiteta Huolehdittava että tyhjästä puskurista ei lueta 812315 Ohjelmiston rakentaminen, Asynkronisuus 24
III.2 Lukija-kirjoittajamalli Alunperin mallinsi tietokannan toimintaa Yleishyödyllinen rinnakkaisessa ohjelmoinnissa Voidaan käyttää säätelemään pääsyä monenlaisiin resursseihin Kahdenlaisia säikeitä: lukijasäikeet ja kirjoittajasäikeet resurssia saa lukea moni säie yhtä aikaa resurssia voi päivittää vain yksi (kirjoittaja)säie resurssia ei saa lukea jos kirjoitetaan Abstrakti luokka ReadAndWRite mallintaa toimintaa, operaatiot readoperation() ja writeoperation() määriteltävä perivässä luokassa 812315 Ohjelmiston rakentaminen, Asynkronisuus 25
IV Eloisuusongelmat Lukkiutuminen (deadlock) Yleinen syy säikeiden jumittumiseen Säikeet odottavat ristiin toistensa lukitsemien resurssien vapautumista Uloslukkiutuminen Koodi voi sisältää sisäkkäisiä olioita joilla synkronoituja metodeja -> Säie voi lukita itsensä ulos Menetetty signaali Säie odottaa signaalia, joka on tuotettu ennen kuin säie alkoi odottaa 812315 Ohjelmiston rakentaminen, Asynkronisuus 26
V Javan edistyneempi rinnakkaisuuden hallinta Javan versioon 5.0 lisätukea rinnakkaisuuden hallintaan java.util.concurrent java.util.concurrent.locks java.util.concurrent.atomic Em. Pakkaukset sisältävät Erilaisia synkronointiprimitiivejä, Rinnakkaisessa ohjelmoinnissa käyttökelpoisia kokoelmia, Säieturvallisia muuttujatyyppejä 812315 Ohjelmiston rakentaminen, Asynkronisuus 27
V.1 Semaforit Semafori: kokonaislukumuuttuja, jonka arvo on lupien lukumäärä Säie pyytää lupaa semaforilta: Jos arvo suurempi kuin nolla, säie saa luvan ja arvoa vähennetään Jos arvo nolla, säie jää odottamaan, kunnes lupien määrä kasvaa Kun säie luopuu luvasta, semaforin arvoa kasvatetaan Voidaan käyttää poissulkevuuteen tai synkronoituna laskurina 812315 Ohjelmiston rakentaminen, Asynkronisuus 28
V.1 Semaforit (2) Javassa semafori luokka Semaphore, pakkauksessa java.util.concurrent Käyttö: // Luodaan semafori, jolla alussa yksi lupa Semaphore sema = new Semaphore(1); // Luvan pyytäminen sema.acquire(); // Luvan vapauttaminen sema.release(); 812315 Ohjelmiston rakentaminen, Asynkronisuus 29
V.2 Lukot ja ehtomuuttujat Pakkauksessa java.util.concurrent.locks rajapinta Lock Erilaisia implementointeja -> perussynkronointia monipuolisempi toteutus Lukkoihin voidaan liittää Condition-rajapinnan toteuttavia olioita vastaamaan ehtomuuttujia Käyttöidiomi kilpailun synkronoinnille Lock lukko =...; // Sopiva lukko-olio lukko.lock(); try { // Käytä lukon suojaamaa resurssia } finally { lukko.unlock(); // Varma vapauttaminen } 812315 Ohjelmiston rakentaminen, Asynkronisuus 30
V.2 Lukot ja ehtomuuttujat (2) Luokan ReentrantLock olio Javan monitorin yleistys Olioon voi liittyä useita ehtomuuttujia (Condition), luodaan luokan metodilla newcondition() Condition-olion odotusmetodi await() ja ilmoitusmetodit signal() sekä signalall() Vastaavat Object-luokan metodeja wait(), notify() sekä notifyall() Katso esimerkkiohjelmista CircularBufferWithLocks.java Tehtäviä: Poista ohjelmista RaceConditionRunnable.java ja UnsafeTicketOffice.java kilpailutilanne lukkoja käyttämällä. Muuta aiemmin tehty PingPong-ohjelma käyttämään lukkoja. 812315 Ohjelmiston rakentaminen, Asynkronisuus 31
V.3 Suorittajat ja säievarastot Javan pakkaukseen java.util.concurrent sisältyy rajapinta Executor, jonka avulla voidaan eristää tehtävän välitys ja sen suoritusmekanismi toisistaan. Metodi void execute(runnable r) Toteuttamalla rajapinta saadaan erilaisia suorittajia Luokissa voidaan käyttää Executor-oliota ja sitoa se vasta myöhemmin tietyntyyppiseen suorittajaan, esim class SimpleExecutor implements Executor{ public void execute(runnable r){ new Thread(r).start(); } } 812315 Ohjelmiston rakentaminen, Asynkronisuus 32
V.3.1 Säievarastot Kiinteä määrä säikeitä, joille annetaan tehtäviä suoritettavaksi Vähentää tarvittavien säikeiden määrää, jos tehtävien ei tarvitse olla yhtä aikaa ajossa Javan luokka ThreadPoolExecutor Olio saadaan kutsumalla Executors-luokan staattista metodia newfixedthreadpool(int poolsize) Tehtävä käynnistetään luokan metodilla execute -> pääsee suoritukseen kun varaston säie vapautuu 812315 Ohjelmiston rakentaminen, Asynkronisuus 33
V.3.1 Säievarastot (2) Javan luokka ThreadPoolExecutor jatkuu Luokan metodi shutdown() -> tehtävät ajetaan loppuun, uusia ei oteta Luokan metodi awaitfortermination() odottaa kunnes kaikki tehtävät suoritettu Ks. esimerkit NQueensWithPool.java ja NQueensWithPoolLatch.java Tehtävä: Jälkimmäisessä CountDownLatch odottaa tehtävien valmistumista. Korvaa se semaforilla. 812315 Ohjelmiston rakentaminen, Asynkronisuus 34
VI Tehtävät Jokainen palauttaa oman ratkaisun Seuraavista neljästä tehtävästä valitaan kaksi Kuvaukset erillisessä dokumentissä 1. Boolimaljasimulaatio 2. Kanjonia ylittävät turistit 3. Huvipuiston vuoristorata 4. Neljä samaa numeroa -peli 812315 Ohjelmiston rakentaminen, Asynkronisuus 35