210 likes | 414 Views
Konkurencia vs. paralelizmus. k ompo zícia nezávislých výpočtov spôsob myslenia, ako výpočet (pr ácu ) rozdeliť medzi nezávislých agentov keďže okolitý svet je paralelný, je to spôsob ako lepšie interagovať s ním málo kto z nás má skutočne paralelný HW, max. tak 2-,4-, 8-jadro...
E N D
Konkurencia vs. paralelizmus • kompozícia nezávislých výpočtov • spôsob myslenia, ako výpočet (prácu) rozdeliť medzi nezávislých agentov • keďže okolitý svet je paralelný, je to spôsob ako lepšie interagovať s ním • málo kto z nás má skutočne paralelný HW, max. tak 2-,4-, 8-jadro... • na jednom procesore paralelizmus neurobíte, ale konkurentný výpočet áno ale ... • konkurentný výpočet na jednom procesore budepravdepodobne pomalší ako sekvenčná verzia, takže viac ide o konkurentnú paradigmu ako o čas Go konkurencia založená na CommunicatingSequentialProcesses (T. Hoare, 1978) poskytuje: • konkurentné procedúry (tzv. gorutiny) • synchronizáciu a komunikáciu prostredníctvom kanálov • príkaz select http://www.youtube.com/watch?v=f6kdp27TYZs
Gorutina - príklad Gorutina loopForever sa vykonáva ako funkcia loopForever len sa nečaká na jej výsledok, resp. skončenie packagemain import("fmt""math/rand""time") funcloopForever(taskstring){ fori:=1;;i++{// počítame do nekonečna fmt.Printf("%s:%d\n",task,i) time.Sleep(time.Duration(rand.Intn(500))*time.Millisecond)}} funcmain(){ goloopForever("prvy") // spustenie 1.gorutiny goloopForever("druhy")// spustenie 2.gorutiny varinputstring// toto čaká na input, v opačnom fmt.Scanln(&input)// prípade, keď umrie hlavné vlákno fmt.Println("mainstop")} // umrie v Go všetko... concurrent1.go
Gorutina Gorutina • je nezávisle vykonávaná funkcia • má vlastný stack, ktorý rastie/scvrkáva sa podľa jej potrieb • môže ich byť veľa, aj veľmi veľa (uvidíme ~ 100.000) • je to menej ako vlákno (thread), ale k nemu to má najbližšie Anonymná gorutina je de-facto bezmenná funkcia, ktorú aj hneď zavoláme: funcmain(){ gofunc/*tu chýba meno fcie*/ (taskstring){ fori:=1;;i++{// počítame do nekonečna fmt.Printf("%s:%d\n",task,i) time.Sleep(...) } }("prvy")// tu hneď voláme anonymnú fciu s argumentom
Komunikácia a synchronizácia Pomocou kanálov (nebuffrovaná verzia): var ch chan int resp. ch := make(chan int) ch = make(chan int) zápis do kanála je blokujúca operácia, kým hodnotu niekto neprečíta z kanála ch <- 123 čítanie z kanála je blokujúca operácia, až kým hodnotu niekto nezapíše do kanála x = <-ch takže ide o komunikáciu (prenos dát), ale aj o synchronizáciu. V prípade buffrovaných kanálov make(chan int, 10)prídeme o synchronizáciu, tažke to skúsime neskôr, ak vôbec...
Dvaja píšu, jeden číta funcloopAndSend(taskstring,chchanstring){ fori:=1;i<100;i++{ ch<-fmt.Sprintf("%s:%d\n",task,i) time.Sleep(time.Duration(rand.Intn(500))*time.Millisecond) }} funcmain(){ // dve gorutiny píšu do ch:=make(chanstring)// toho istého kanála ch goloopAndSend("prvy",ch) goloopAndSend("druhy",ch) for{ // tu to čítameformsg := range ch { msg:=<-ch// range prebieha obsahom // celého kanála fmt.Print(msg)fmt.Print(msg) }} fmt.Println("mainstop")}//nikdy neskončí, prečo ? concurrent2.go
Funkcia vráti kanál chan string je typ kanála stringov, funkcia ho môže vrátiť ako výsledok funcloopToChannel(taskstring)chanstring{ ch:=make(chanstring)// vytvor kanál gofunc(){// pusti nezávislú gorutinu fori:=1;i<100;i++{ // ktorá píše do kanála ch<-fmt.Sprintf("%s:%d\n",task,i) time.Sleep(...)} }()// argumenty anonymnej funkcie returnch }// vráť kanál ch:chan string funcmain(){ ch1:=loopToChannel("prvy") ch2:=loopToChannel("druhy") for{ fmt.Print(<-ch1) // čo dostaneme ??? fmt.Print(<-ch2) // chápeme už synchronizáciu ?? } concurrent3.go
Kanálový sútok funcmultiplexor(ch1,ch2chanstring)chanstring{ ch:=make(chanstring) gofunc(){ // prvá gorutina for{ch<-<-ch1} // čítaj z ch1 a píš to do ch }() gofunc(){// druhá gorutina for{ch<-<-ch2}// čítaj z ch2 a píš to do ch }() returnch} funcmain(){ ch1:=loopToChannel("prvy") // tretia gorutina ch2:=loopToChannel("druhy")// štvrtá gorutina ch:=multiplexor(ch1,ch2) for{ fmt.Print(<-ch)} concurrent3.go
Select select je príkaz syntaxou podobný switch, à la java funcmultiplexorSelect(ch1,ch2chanstring)chanstring{ ch:=make(chanstring) gofunc(){// jednu gorutinu sme ušetrili :-) for{ select{// select vykoná niektorý neblokovaný caseval:=<-ch1:// komunikačný case-príkaz ch<-val// ak niekto zapísal do ch1 caseval:=<-ch2:// číta sa z ch1, ak ch2, ch<-val// tak z ch2, inak je blokovaný } }// select odpáli nejaká komunikačná udalosť // (zápis/čítanie z/do kanála) v case príkazoch }()// alebo timeout... returnch } concurrent3.go
Select a timeout funcmultiplexorSelect(ch1,ch2chanstring)chanstring{ ch:=make(chanstring) gofunc(){ gameOver:=time.After(10*time.Second) for{ select{ caseval:=<-ch1:ch<-val caseval:=<-ch2:ch<-val case<-time.After(200*time.Millisecond): ch<-"tolate!!!\n" case<-gameOver: ch<-"GAMEOVER\n" close(ch) } }}()returnch} concurrent3.go
Producer-Consumer funcproducer(chchanint){ fori:=1;i<=30;i++{ ch <-i fmt.Println("produce:"+strconv.Itoa(i)) //time.Sleep(time.Second)// lenivá produkcia }} funcconsumer(chchanint){ fori:=1;i<=30;i++{ fmt.Println("consume:",<-ch) time.Sleep(time.Second) // lenivá spotreba }} funcmain(){ ch:=make(chanint,5)// buffrovaný kanál goproducer(ch) goconsumer(ch) time.Sleep(100000000000)}// skoro večnosť producerconsumer.go
Čínsky šepkári varnumber=100000 funcmain(){ start:=time.Now() prev:=make(chanint) first:=prev// ľavé ucho (ľ.u.) nultého šepkára gofunc(){first<-0}() // nultému šepneme 0 do ľ.u. fori:=0;i<number;i++{// 40000 číňanov next:=make(chanint)// kanál z p.u.i-teho gofunc(from,tochanint){// do ľ.u. i+1-vého for{to<-1+< from}// šepnem ďalej 1+čo }(prev,next) // počujem prev=next // pokračujem, i k i+1 } elapsed:=time.Since(start) fmt.Println(<-prev)fmt.Println(elapsed)} chinees.go
Prvočísla(Eratosténovo sito) prev:=make(chanint) first:=prev gofunc(){ fori:=2;;i++{first<-i}}()// do first sypeme 2,3,4, … fori:=0;i< 10000;i++{ // čínski preosievači prvočísel prime:=<-prev// prvé preosiate musí byť prvočíslo fmt.Println(prime) next:=make(chanint)// kanál pre ďalšieho preosievača gofunc(primeint,from,tochanint){// číta z from, píše do for{// do to, vyčiarkne deliteľné prime val:=<-from// číta z from – vstupný kanál ifval%prime>0{// je deliteľné prime ? to<-val// ak nie je, píš do to - výstupný } } }(prime,prev,next)// spustenie nezávislého preosievača prev=next// výsledok ide ďalšiemu osievačovi } chineprimes.go
Quicksort - pivotizácia Nekonkurentná pivotizácia, nepekné dvojité testy … funcpivot(pole[]int)int{ i,j,pivot:=1,len(pole)-1,pole[0] fori<=j{ // hľadanie maxiputána medzi liliputánmi fori<=j&&pole[i]<=pivot{i++} // hľadanie liliputána medzi maxiputánmi forj>=i&&pole[j]>=pivot{j--} ifi<j{// nájdení kandidáti sa vymenia pole[i],pole[j]=pole[j],pole[i] } }// pivota pichni medzi liliputánov a maxiputánov pole[0],pole[j]=pole[j],pole[0] returni } quicksort.go
Quicksort funccquickSort(pole[]int,donechanbool){ iflen(pole)<=1{ done<-true }else{ index:=pivot(pole) left,right:=make(chanbool),make(chanbool) gocquickSort(pole[:(index-1)],left) gocquickSort(pole[index:],right) done<-(<-left&&<-right) } } funccquickSort(pole[]int,donechanbool){ iflen(pole)<=1{ done<-true }elseiflen(pole)<granularity{ squickSort(pole) done<-true }else{ index:=pivot(pole) left,right:=make(chanbool),make(chanbool) gocquickSort(pole[:(index-1)],left) gocquickSort(pole[index:],right) done<-(<-left&&<-right) } } funccquickSort(pole[]int,donechanbool){ iflen(pole)<=1{ }else{ index:=pivot(pole) cquickSort(pole[:(index-1)],left) cquickSort(pole[index:],right) } } quicksort.go
Quicksort výsledky Size Granularity time 500.000500.000 109ms 500.00050.000 62ms 500.0005.000 78ms 500.000500 62ms 500.00050 171ms 500.0005 1375ms 500.000 1 niet dosť kanálov...
Fibonacciho agenti Vyrobíme niekoľko nezávislých agentov, ktorí • zipf(ch1,ch2chanint,ffunc(int,int)int)chanint spája dvojice prvkov z kanála ch1 a ch2 pomocou funkcie f (u nás +) • tail(ch1chanint)chanint číta z kanála ch1, priamo píše do výstupu, akurát prvý prvok z ch1 zabudne • funcfib1()chanint podivným spôsobom generuje fibonacciho čisla... aj to len trochu... • spliter(chchanint)(ch1chanint,ch2chanint) číta z ch, a výsledky konkurentne kopíruje do ch1 aj ch2
Agent zip funczipf(ch1,ch2chanint,ffunc(int,int)int)chanint{ ch:=make(chanint) zipCount++ gofunc(){ for{ f1:=<-ch1 // číta dvojice f1 f2:=<-ch2 // f2 z ch1 a ch2 ch<-f(f1,f2)// píše f(f1, f2), alias f1+f2 } }() returnch } fibStream.go
Agent tail functail(ch1chanint)chanint{ ch:=make(chanint) tailCount++ <-ch1 // prvý prvok zabudne gofunc(){ for{ ch<-<-ch1 } }() returnch } fibStream.go
Agent fib1(katastrofické výsledky) funcfib1()chanint{ ch:=make(chanint) fibCount++ gofunc(){ ch<-1 ch<-1 forval:=rangezipf(fib1(),tail(fib1()), func(x,yint)int{returnx+y}){ ch<-val } }() returnch } fib (fibCount, zipCount, tailCount) 1 (1,0,0) 1 (1,0,0) 2 (7,1,3) 3 (23,7,11) 5 (63,31,31) 8 (255,71,127) 13 (1023,255,511) 21 (2111,1023,1055) 34 (8191,4095,4095) fibStream.go
Agent splitter funcspliter(chchanint)(ch1chanint,ch2chanint){ ch1=make(chanint) ch2=make(chanint) spliterCount++ gofunc(){ for{ val:=<-ch //ch1<-valdeadlock!why? //ch2<-val gofunc(){ch1<-val}() gofunc(){ch2<-val}() } }() returnch1,ch2 } fibStream.go
1 (1,0,0, 0) 1 (1,0,0, 0) 2 (5,2,4, 4) 3 (8,6,7, 7) 5 (12,9,11, 11) 8 (15,13,14, 14) 13 (19,16,18, 18) 21 (22,20,21, 21) .......... 40.Fibonacciho číslo 165580141 (138,135,137, 137) Success: process exited with code 0. Agent fib(prijatelné výsledky) funcfib()chanint{ ch:=make(chanint) fibCount++ gofunc(){ ch<-1 ch<-1 ch1,ch2:=splitter(fib()) // použitie splittera forval:=rangezipf(ch1,tail(ch2), func(x,yint)int{returnx+y}){ ch<-val } }() returnch } fibStream.go