niedziela, 22 lutego 2015

Te i przyszłe warsztaty ze scali

Na horyzoncie są warsztaty Play+Akka, sama Akka, więcej Scali, programowanie funkcyjne oraz do tego uwaga - BigData i moze jakieś Machine Learning. Ale najpierw Wspomnienia po ostatnich warsztatach.

Dodatek do warsztatów pierwszych

W opisie do zajęć : Opis do zajęć z wątków były wątki, kolejki, egzekutory i forkjoiny ale zapomniałem dodać najfajniejsze czyli Java8 i Scala :

Dzięki nowemu api w Javie 8 implementacja prostych przekształceń na wielu wątkach jest banalna :

public static void main(String[] args) {
  OptionalInt result = IntStream.range(0, 100)
  .parallel() //1
  .map(e -> e * 2)
  //.map(e ->{System.out.println("thread : "+Thread.currentThread().getName());return e;}) //2
  .reduce(Integer::sum);
  
  result.ifPresent(System.out::println);
 }
  1. Ta jedna linia wystarczy aby obliczenia odbywały się w wielu wątkach
  2. Na dowód można sobie odkomentować tężę linijkę i zobaczyć, że w konsoli pojawią się różne wątki z domyślnej puli
thread : ForkJoinPool.commonPool-worker-7
thread : ForkJoinPool.commonPool-worker-2
thread : ForkJoinPool.commonPool-worker-1
thread : ForkJoinPool.commonPool-worker-4
thread : ForkJoinPool.commonPool-worker-4
thread : ForkJoinPool.commonPool-worker-3
thread : ForkJoinPool.commonPool-worker-6
9900

I Scala dla porównania. Jest trochę mniej tekstu i przez to raczej czytelniej.

def main(args: Array[String]) {
    val result=(1 to 100)
    .par //1
    .map(_*2)
    //.map(e=>{println(s"thread : ${Thread.currentThread()}");e}) //2
    .sum
    
    println(result)
  }
thread : Thread[ForkJoinPool-1-worker-5,5,main]
thread : Thread[ForkJoinPool-1-worker-11,5,main]
thread : Thread[ForkJoinPool-1-worker-3,5,main]
thread : Thread[ForkJoinPool-1-worker-9,5,main]
thread : Thread[ForkJoinPool-1-worker-3,5,main]
thread : Thread[ForkJoinPool-1-worker-9,5,main]
thread : Thread[ForkJoinPool-1-worker-9,5,main]
thread : Thread[ForkJoinPool-1-worker-9,5,main]
9900

Dlaczego warto się uczyć - ekonomia

To co widzimy powyżej to charakterystyka diody półprzewodnikowej (chyba, bo już 15 lat nie lutowałem). Poza tym, że jest dioda jest podstawowym składnikiem przemysłu elektronicznego to również koncepcja napięcia przebicia pełni rolę doskonałej metafory tegoż przemysłu.

Generalnie chodzi o to, że społeczność czy ogólnie pojęci programiści muszę w danym ekosystemie wygenerować odpowiednie "napięcie edukacyjne" zanim nastąpi przebicie ciekawych projektów. Jeśli np. będą chętni na zlecenie do Łodzi projektów w Scala,Play czy Akka to kto to zrobi jak mało kto umie? Dlatego też systematyczna edukacja, chociaż na początku wydaje się nie przynosić żadnych rezultatów (tak do 0,6-0,7 Volta jeśli dobrze pamiętam) może być kurą, która rozwiąże dylemat tego co było pierwsze..

Dlaczego warto się uczyć - psychologia

W przypadku nauki rzeczy, która nie przynosi nam natychmiastowych profitów mamy do czynienia z tzw. efektem odroczonej gratyfikacji Psychologia Współczesna - odroczona gratyfikacja. Generalnie sposobem najłatwiejszym do polecenia i jednocześnie najtrudniejszym do wdrożenia to "się zmusić". Bardzo pomaga np. siłka jako trening powtarzalnego wysiłku.

Plus przyda się świadomość efektu, który powszechnie nazywa się słomianym zapałem. To ma miejsce wtedy kiedy np. przewidujemy, że jak tylko usiądziemy do nauki Scali to od razu stanie się coś magicznego albo wtedy gdy bo jednej serii z hantlem spodziewamy się przyrostu bicepsa do 45 cm. Po pierwszym szoku od rzeczywistości umysł rewaluuje bilans zysk i strat i stwierdza, że jednak lepiej usiąść przed monitorem/tv i żyć życiem postaci z seriali czy gier komputerowych. Ponownie trzeba znaleźć sposób by się jakoś zmusić do wysiłku.

Dlaczego warto się uczyć - socjologia

Niektórzy ludzie wydaja się być bardzo zmotywowani poprzez rzeczy, które mogą wrzucić na fejsa i zebrać lajki. Gdyby tylko udało się jakoś powiązać ten mechanizm z faktem nauki czegokolwiek... (oczywiście nie chodzi tutaj o mechanizmy używane przez fejsbukowe aplikacje próbujące sztucznie wygenerować autolans poprzez broadcast informacji, ze ktoś w jakiejś grze zdobył odznakę czy level - co jest kurwa zwyczajnie żenujące. )

Kolejne warsztaty

W planach było zrobienie drugiej części warsztatów z wielowątkowości jakkolwiek są ciekawsze tematy :

  • Play+Akka - marzec/kwiecień
  • Scala i Obiektówka jakiej jeszcze nie widzieliście - marzec/kwiecień
  • Programowanie funkcyjne - to będzie niedługo być/ nie być
  • I kolejna ciekawa rzecz Scala/Akka + DDD. Na manningu są trzy MEAPy an ten temat.To się teraz podobno nazywa DDDD z Distributed na początku. kolejne D to będzie Double Distributed. I tak najlepszą nazwę miał język do programowania radzieckich głowic atomowych CCCP++.
  • I ogromny dosłownie temat na koniec - BigData. Pojawiają się coraz to ciekawsze narzędzia a Spark ma tak wygodną konsolę, ze można poprowadzić warsztaty bez pracochłonnej konfiguracji.

I na koniec kilka ćwiczeń, które były przeznaczone na drugą część warsztatów z wielowątkowości. Na razie do samodzielnej pracy a w przyszłości może coś się z tego zorganizuje. W java.util.concurrent można znaleźć wiele ciekawych klas rodem ze startreka jak np.Phasery.

Konstrukcje

Podobno większość rzeczy z tych ćwiczeń trzeba było kiedyś przy pomocy wait/notify robić ale tego nie pamiętam - za młody jestem.

ThreadLocal

ThreadLocal zapisuje dane w kontekście konkretnego wątku i inne wątki się w te dane nie wcinają. Patrzymy na przykład.

object ThreadLocalExercise {

  val statistics=new ThreadLocal[List[Int]]{
    override def initialValue()=List()
  }
  
  def main(args: Array[String]) {
    val ts=Vector.tabulate(3)(i=>new Worker(s"worker ${i}"))
    ts.foreach(_.start())
    ts.foreach(_.join())
  }
  
  class Worker(n:String) extends Thread{
    override def run()={
      val r=new Random()
      gatherStatistics(r)
      displayStatistics
      cleanStatistics //1
    }

    def gatherStatistics(r: java.util.Random) = {
       (1 to 3).foreach{i=>
        val v=r.nextInt(10)
        val l=statistics.get()
        println(s"thread ${n} is adding value ${v}")
        statistics.set(v::l)
        }
    }
    
    def displayStatistics=println(s" worker ${n} has ${statistics.get}")
    def cleanStatistics=statistics.remove()
  }
}

  1. To dosyć ciekawy moment. Otóż okazuje się, że nie czyszcząc ThreadLocal można doprowadzić do wycieku pamięci na serwerze gdzie dany wątek wróci do puli i będzie za chwilę obsługiwał inny request
    • http://avasseur.blogspot.com/2003/11/threadlocal-and-memory-leaks.html
    • http://frankkieviet.blogspot.com/2006/10/classloader-leaks-dreaded-permgen-space.html
    • http://frankkieviet.blogspot.com/2006/10/how-to-fix-dreaded-permgen-space.html
Rozwiązaniem może być użycie WeakReference
val statistics=new ThreadLocal[WeakReference[List[Int]]]{
    override def initialValue()=new WeakReference(List())
  }

CountDownLatch

Pejzaż poniżej przedstawia koncepcję działania CountDownLatch. Wątki wywołujące await grzecznie czekają aż coś zwolni blokadę - a blokad jest tyle jaka cyferka na początku czyli w naszym przypadku dwie

object CountDownLatchExercise {

  def main(args: Array[String]) {
    val cl=new CountDownLatch(2)
    
    val c1=new Controller(5,cl)
    val c2=new Controller(6,cl)
    
    val rockets=(1 to 10).map(new Rocket(_,cl))
    rockets.foreach(_.start)
    
    c1.start()
    c2.start()
    
    rockets.foreach(_.join)
  }
  
  class Rocket(n:Int,cl:CountDownLatch) extends Thread{
    
    private val r=new Random()
    
    override def run()={
      TimeUnit.SECONDS.sleep(r.nextInt(4))
      println(s"przygotowania rakiety ${n}")
      cl.await()
      println(s"start rakiety ${n}")
    }
  }
  
  class Controller(sleep:Int,cl:CountDownLatch) extends Thread{
    override def run()={
      TimeUnit.SECONDS.sleep(sleep)
      println(s"kontroler zwolniony po ${sleep} sekundach")
      cl.countDown()
    }
  }
}
I wynik :
przygotowania rakiety 5
przygotowania rakiety 6
przygotowania rakiety 4
przygotowania rakiety 2
przygotowania rakiety 8
przygotowania rakiety 3
przygotowania rakiety 10
przygotowania rakiety 7
przygotowania rakiety 1
przygotowania rakiety 9
kontroler zwolniony po 5 sekundach
kontroler zwolniony po 6 sekundach
start rakiety 6
start rakiety 2
start rakiety 3
start rakiety 4
start rakiety 9
start rakiety 5
start rakiety 1
start rakiety 7
start rakiety 10
start rakiety 8

Bariera

Dzieło poniżej pokazuje, iż wątki czekają aż wszystkie odhaczą się w barierze (t1), potem ruszają dalej itd. Do tego po każdej kolejce barman dolewa wódy.

object CyclicBarrierExercise {

  def main(args: Array[String]) {
    val b=new CyclicBarrier(5,new Runnable{
      def run={
        println("barman dolewa wódy")
      }
    })
    
    
    val threads=Vector.tabulate(5)(i=>new Shot(b,i+1)).map(new Thread(_))
    threads.foreach(_.start())
    
  }
  
  class Shot(b:CyclicBarrier,i:Int) extends Runnable {
    def run(): Unit = {
      println(s"wątek ${i} pierwsze kolejka")
      b.await()
      println(s"wątek ${i} druga kolejka")
      b.await()
      println(s"wątek ${i} trzecia kolejka")
      b.await()
      println(s"wątek ${i} do dna")
    }
  }
}
wątek 2 pierwsze kolejka
wątek 1 pierwsze kolejka
wątek 3 pierwsze kolejka
wątek 5 pierwsze kolejka
wątek 4 pierwsze kolejka
barman dolewa wódy
wątek 3 druga kolejka
wątek 4 druga kolejka
wątek 5 druga kolejka
wątek 1 druga kolejka
wątek 2 druga kolejka
barman dolewa wódy
wątek 2 trzecia kolejka
wątek 1 trzecia kolejka
wątek 4 trzecia kolejka
wątek 5 trzecia kolejka
wątek 3 trzecia kolejka
barman dolewa wódy
wątek 3 do dna
wątek 1 do dna
wątek 5 do dna
wątek 4 do dna
wątek 2 do dna