fredag 17. september 2010 Diverse prosjekter Ping Ring Clojure Samtidighet
Dette er del 7 i artikkelserien Ping Ring hvor jeg implementerer et og samme program i et utall ulike programmeringsspråk - for å se om det er noe å lære gjennom å gjøre det. Introduksjonen kan du lese her.
Clojure har noe som kalles agents. Jeg skjønte til å begynne med lite av hvordan jeg burde bruke dem, men etter å ha sett Rich Hickeys to og en halv timers lange demonstrasjon av concurrency i Clojure (anbefales) følte jeg meg klar for å gjøre et forsøk.
Mye av koden nedenfor er lik den du finner i del 6. En detalj er at jeg har endret hvordan jeg samler opp kommandolinje-argumentene. I orginalversjonen lagret jeg dem i et struct-map, og sendte dem rundt til alle funksjonene. Denne gangen har jeg bare lagret dem i fire ulike verdier definert globalt i namespacet, og bruker dem direkte i funksjonene (ikke helt som Thomas foreslo altså).
Hovedforskjellen er derimot hvordan jeg har implementert den tråden som skal aktivere alarmen om det ikke kommer pings innenfor et visst tidsrom. Her har jeg nå brukt en agent, definert i linje 26. Agenten har en verdi – i utgangspunktet 0 for denne agenten – og verdien kan endres gjennom å sende den en fuksjon. Funksjonen jeg sender agenten (ved oppstart) er check-delay, definert i linje 32. Innparameter til funksjonen er alltid nå-verdien til agenten. Resultatet av funksjonen vil bli den nye verdien.
Alerter-agenten fungerer slik at den først venter så lenge som det er akseptabelt å ikke motta ping. Deretter sjekker den om det har ankommet en ping. Til dette bruker jeg et atom – en spesiell referansetype som egner seg for samtidighet – som er definert i linje 9, og som settes av listen-for-pings når en ping har blitt mottatt. Jeg har altså gått bort fra å bruke tidspunkt for når ping ble mottatt, og har nå en løsning som ligner mer på den jeg lagde i Erlang – bare i et mere behagelig språk!
Hvis ping var mottatt resetter agent-funksjonen atomet til false, og returnerer 0 (ingen alarmer aktivert). Hvis ping ikke var mottatt vil funksjonen trigge alarmen, og da bruker den agent-verdien sin til å beregne hvor mange sekunder det har gått siden sist ping (antall alarmer på rad ganger antall sekunder forsinkelse tillatt).
Men før funksjonen gjør dette – i linje 35 – køer den opp et nytt kall til seg selv. *agent* er nemlig en referanse til den agenten funksjonen kjører på. Agenter er alltid synkroniserte, dvs. at et nytt kall til agenten ikke vil starte før det forrige kallet er avsluttet. Dermed fungerer dette altså nærmest som en uendelig rekursjon.
Jeg vet ikke om jeg helt klarte å formidle hvordan agenten fungerte, men tar du en god titt på koden i tillegg burde det hele bli klarere:
Alt i alt synes jeg denne løsningen ble hakket mer elegant enn den forrige. Jeg slipper å rote med datoer, og den er faktisk også mer presis i når den utløser alarmen. For rasjonalet bak å bruke agenter må jeg henvise deg til Rick Hickey eller andre som har blogget om temaet. Dette var bare en liten demo av en av måtene de kan brukes på. Jeg lover derimot mer om samtidighet i Clojure i fremtidige blogposter.
Tidligere i serien: Introduksjon | Del 2 (C#) | Del 3 (Ruby) | Del 4 (Boo) | Del 5 (Erlang) | Del 6 (Clojure).
Kildekoden fra denne blogposten er tilgjengelig på Github. Der står du fritt til å forgrene løsningen og gjøre egne modifikasjoner om du ønsker det (for å illustrere et poeng eller lignende). Som alt annet på bloggen er koden lisensiert under Creative Commons.