Go... Go... na tańce .. Go , Go , Go ....

(Z Wikipedii) "wieloparadygmatowy język programowania opracowany przez pracowników firmy Google: Roberta Griesemera, Roba Pike'a oraz Kena Thompsona. Łączy w sobie łatwość pisania aplikacji charakterystyczną dla języków dynamicznych (np. Python, Lisp), jak również wydajność języków kompilowanych (np. C, C++)."
ODPOWIEDZ
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

Jak już wiecie w miarę jedzenia apetyt rośnie.
Zdążyłem się już nieco przyzwyczaić do składni i poznać niektóre tajniki, wiec w moim uznaniu przyszła pora na kontynuację. Po doczytaniu i doszkoleniu się mogę śmiało przyznać że GoLang ma swoje zalety. Poza faktem ze jest open-sourcowy , ma stosunkowo prostą składnię co zdecydowanie ułatwia uczenie, Go ma też całkiem sporą wydajność (choć jak wspominałem binarki do najmniejszych nie należą),
a to powoduje z kolei że programy w nim pisane mają szerokie zastosowania w "backendzie".

Z zalet więc śmiało mogę wymienić :

--- prosta składnia
--- open-source (pokopać sobie można w kodzie kompilatora (co też pozwala na poznanie różnych ciekawostek))
--- ujednolicone i wbudowane w język nazewnictwo struktur i funkcji
--- ujednolicony styl , który wreszcie ucina debaty wielkich programistów na temat jakie wcięcia mają być w kodzie
--- kompilator w miarę inteligentnie informuje nas ERROREM gdy z uporem maniaka importujemy nie potrzebne pakiety.
--- ponadto takie małe ustrojstwo jak goroutines (o czym napiszę inszą razą) ułatwia i wspomaga nas przy programowaniu wielowątkowym.

Wygląda to naprawdę dobrze, a jestem na początku drogi i traktuję naukę Go jako dobrą zabawę i oderwanie od
pracy ot tak na rozluźnienie mięśni. Stąd wymowny tytuł tego arta wskazujący na skecz kabaretu Ani Mru Mru ...



bo moi drodzy tak odbieram w tej chwili naukę tego języka jak dobrą zabawę. Ale jak się okazuje może być strzałem w 10 bowiem Go jest jednym z lepiej opłacanych języków programowania (oczywiście nie zamierzam zostać płatnym zabójcą z korpo klepiącym w Go)
ale dla młodszych adeptów może być to jakaś wskazówka dla obrania kierunku rozwoju. Po tak wymownym wstępie wielu z was zapewne się zastanawia gdzie go by zastosować, bo na przykład Python'a wielu z nas używa do szybkiej automatyzacji różnych czynności.
No tu was zaskoczę , ale poczytałem to i owo w dokumentacji języka dostępnej TU,
przekopałem się przez tutorial, który z pomocą wspomnianych w poprzednim wpisie
książek pozwala mi go łatwiej rozumieć i poznawać oraz się uczyć. Zauważyłem, że świetnie daje sobie radę przy budowaniu API ,
parsowaniu danych JSON, XAML , YAML czy konstruowaniu mikroserwisów czy małych apkach serwerowych.

Ale nie piszę kolejnego Wstępniaka w stylu Pampucha, więc dla oderwania od tej hmm .. prozy
Tu macie darmowy WeBook/ebook An Introduction to Programming in Go Autorstwa Caleb'a Doxey'a w języku angielskim dostępny właśnie jako hmm chyba wymyśliłem nowe słowo (WeBook) do czytania jako książkostrona i jako tradycyjny PDF.

I teraz chyba przejdziemy to takiego w miarę prostego mikrokursu, w sumie tak bo nie mam zamiaru jak co niektórzy klepać wkoło jakie są typy zmiennych , kręcić 20 filmow po minimum 120min o funkcjach, strukturach
by zrobić sobie lukę na kolejne kilka godzin wideo z lasu o alternatywnych rozwiązaniach tylko po to by znów wracać na początek .... nie to wydaje mi się pozbawione sensu gdyż zwracam się niejako do ludzi, którzy programują w takim czy innym języku i znają podstawy, a ci który by chcieli zacząć przygodę z programowaniem właśnie od Go i nie mieli styczności z programowaniem szybko dostrzegą, że wskazane materiały doskonale omawiają i wyczerpują temat podstaw i opisów. Dlatego tak na szybko postaramy się bezboleśnie wejść w kod ...
(xbary wygoń kota)
...

Wiecie najważniejsze by przyswoić sobie składnię i jak to najlepiej zrobić - pokazać w prostym kodzie
i omówić ... gdyż to co się ukaże będzie nas prześladować w każdym programie niczym wspomniany kot xbarego
śpiący na klawiaturze :)
  1.  
  2. package main
  3.  
  4. import (
  5.         "fmt"
  6. )
  7.  
  8. func main() {
  9.         fmt.Println("Hello World!")
  10. }
  11.  
No jak widać znów jest to ten ohydny i paskudny Hello World , ale właśnie w nim możemy zobaczyć wszystko to
co stanowi kwintesencję języka czyli właśnie jego składnię. Więc tak prostymi słowami ...
w Go program to taka paczka w której chowamy różne drobiazgi. I tak najprościej sobie to wyobrazić.
więc mamy paczkę main (od której zawsze zaczyna się wykonywanie programu) i paczek może być wiele
cała ciężarówka jak przy przeprowadzce ... dlatego właśnie w pierwszym wierszu widzimy package main. jako ze tu niema wiele w tym kodzie ... mamy tylko jedną paczkę i to małą ... ale kazda biblioteka itd ... w większych programach będzie również paczką . Myślę ze paczka będzie bardziej obrazowa od skrótów myślowych z pierwszego arta o go :)

ale wróćmy do tematu ...
Wiec mając tą naszą paczkę "main" wkładamy do niej inną paczuszkę , albo kilka i to właśnie deklarujemy poprzez import ... czyli nasz program będzie używał paczuszki "fmt" .
fmt to taki pakiet/biblioteka który będzie zwyczajowo w niemal każdej paczce (coś jak wypełniacz) bowiem zawiera on usprawnienia formatowania i wypisywania danych.

dalej mamy jedyną naszą funkcję i do tego główną main() nie zwraca ona żadnych wyników i nie przyjmuje żadnych argumentów. Wniej znajduje się cały kod program zwany ciałem , a mieści się w obrębie nawiasów klamrowych { }. W tym banale mamy aż jedną linijkę kodu czyli nic wielkiego zwyczajne wywołanie publicznej funkcji Println, która znajduje się w paczce fmt a argumentem tej funkcji jest string "Hello World"
...

Prawda że składnia jest banalna i jak już wspominałem obywamy się bez kończenia wierszy ";"
dlaczego jednak napisałem funkcja publiczna ... no właśnie jak wspominałem wcześniej niema w Go modyfikatorów dostępu, ale gdy nasza funkcja ma nazwę pisaną dużym znakiem jest właśnie publiczna.
jeśli małymi znakami jest prywatna i nie możemy z niej skorzystać w innych paczkach poza tą w której jest.

I to w zasadzie wszystko co na tą chwile powinniśmy wiedzieć o składni -- prawda że prosto ?
W następnym wpisie napiszemy już jakiś program ze zmiennymi i stałymi nieco bardziej wyrafinowany
od Hello World , ale równie banalny.
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Re: Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

Dziś jest dobry dzień n kontynuacje wywodu. Najlepsza forma nauki programowania czy to w nowym języku czy też od zera to moim zdaniem
znaleźć problem i go sobie rozwiązać. No tak łatwo powiedzieć , ale co tam ... no ale przydało by się nie .... coś co zmusi do myślenia jak
problem. Zanim jednak cos mi wpadnie do głowy warto jak obiecałem przyglądnąć się zmiennym i typom.

W Go znajdziemy trzy typy podstawowe:

---> Typy Liczbowe

Jak wiecie czy się domyślacie reprezentują one rózne wartości liczbowe czy to przechowywane przez zmienne czy też zwracane przez funkcję
są tu wiec zarówno liczby całkowite , zmiennoprzecinkowe i złożone. Mamy więc Int8 , int16, int32, int64, uint8, uint16, uint32, uint64, float32
float64 -- nic specjalnego ot liczby całkowite ze znakiem , bez znaku i zmiennoprzecinkowe, ale są też complexy;

complex64 i complex128 -- zawierają one float32 w części rzeczywistej i urojonej jakkolwiek to brzmi :)

---> Typy ciągów

One reprezentują sekwencję bajtów (znaków). Możemy wykonywać różne operacje na ciągach, takie jak łączenie ciągów,
wyodrębnianie podciągów itp.

---> Typy logiczne

Tu sprawa jest bardzo prosta bowiem reprezentują tylko dwie wartości : prawda i fałsz

Zmienne w Go to ciekawa sytuacja, trochę inna niż znamy z innych języków. Mianowicie w Go zmienne wskazują na lokalizację w pamięci, która przechowuje jakąś wartość zaś typ wskazuje na typ tej wartości umieszczony pod danym adresem w pamięci. Powiecie przecież to normalne
no jak się okazuje nie do końca. W Go mamy ciekawostkę jak zmienna nienazwana , dlaczego ?
Bowiem zmienna wskazuje na konkretny adres w pamięci , zatem nie musi mieć nazwy ważne by pod konkretnym adresem były konkretne dane.
Oczywiście zmienne deklarujemy tradycyjnie :
  1.  
  2. var  <nazwa zmiennej> <typ>
  3.  
Możemy teraz przypisać do zmiennej wartość , albo uprościć i przypisać ją podczas deklaracji zmiennej:
  1.  
  2. var zmienna int16 = 10
  3.  
Jednakże Go pozwala nam na deklarowanie zmiennej z przypisaną wartością początkową bez podawania typu:
  1.  
  2. var zmienna = 2.21
  3.  
I język Go na podstawie wartości sam sobie odgadnie jakiego typu jest zadeklarowana zmienna :)
Teraz więc zrobimy sobie taki prosty programik obrazujący deklarację i użycie zmiennych :
  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func main() {
  6.    
  7.     var x int
  8.     x=2
  9.     fmt.Println("x:", x)
  10.    
  11.     var y int=34
  12.     fmt.Println("y:", y)
  13.    
  14.     var z=121
  15.     fmt.Println("z:", z)
  16.    
  17.     var i, j = 512,"Czesc"
  18.     fmt.Println("i oraz j:", i,j)
  19. }
  20.  
  21.  
  22.  
Jak nie trudno się domyślić program wypisze na ekranie konsoli :

x: 2
y: 34
z: 121
i oraz j: 512 Czesc


Dodatkowo możemy podczas deklarowania zmiennej pominąć też słowo kluczowe var
  1.  
  2. zmienna := 3.14
  3.  
Taka forma jest również prawidłowa i Go będzie doskonale wiedziało co chcemy zrobić. Zauważcie, że użyłem := zamiast = i tu taka ciekawostka
nie możemy używać := do przypisania wartości zmiennej którą już zadeklarowaliśmy, bowiem := służy do deklarowania i przypisywania wartości.
Co jednakowoż pozwala przypisać jednym zamachem wiele wartości (i zadeklarować wiele zmiennych). Łatwo zobrazujemy to takim małym kodem:
  1.  
  2. package main
  3. import ("fmt")
  4.  
  5. func main() {
  6.     zmienna := 11
  7.     fmt.Println(zmienna)
  8.  
  9.     zmienna := 44   // ty będzie błąd
  10.     fmt.Println(zmienna)
  11. }
w wyniku dostaniemy błąd gdyż zmienna jest już zadeklarowana i ma przypisaną wartość, początkowo nie wiedziałem o tym i
prowadziło to do zabawnych błędów i frustracji, ale jak zawsze należy się doszkolić zamiast rwać włosy z głowy gdy zobaczymy:

no new variables on left side of := czyli (brak nowych zmiennych po lewej stronie :=)

Warto tu wspomnieć że wszystkie zmienne bez wartości początkowej będą miały wartość 0 dla typów numerycznych , false dla Bolean
i pusty ciąg " " dla stringów. To bardzo przydatne jak się potem okaże ...

Stałe to też zmienne , ale różnią się od w/w tym że ich wartości nie można zmienić po przypisaniu. Deklarujemy je słowem kluczowym const.
i w sumie to by było na tyle w tej chwili bo już trochę się zmęczyłem. a upał za oknem żyć nie daje.
miłej zabawy:)
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Re: Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

Go to zaiste ciekawy język , w każdym języku programowania mamy pętle for , do while , while tymczasem w go mamy tylko pętlę "for"
i żadnej innej. Wydawać by się mogło że ta ubogość wręcz uniemożliwi nam pisanie skomplikowanych programów.
Jej składnia to :
  1.  
  2. for initialisation_expression; evaluation_expression; iteration_expression {
  3.    (wyrażenia)
  4. }
  5.  
Na szczęście działa ona jak for w innych językach wiec choć tu niema jakiś dziwnych obiektów.
  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func main() {  
  6. var i int
  7. for i = 1; i <= 5; i++ {
  8. fmt.Println(i)
  9.     }
  10. }
  11.  
Co jak się domyślacie da wynik w postaci : 1 2 3 4 5

Niemniej wracając do jednej obsługiwanej pętli jak się głębiej zastanowić to w sumie for jest najpowszechniej używana , sam osobiście
rzadko używałem w kodach petli while czy do while wiec poniekąd jest to fajne ułatwienie jest tylko for i kropka :) Na szczęście nie wycięto
wyrażenia warunkowego jakim jest użyteczne If else , a jego składnia nie różni się od tej w innych językach, wiec nie będę sie tu rozpisywał
gdyż każdy wie jak ten mechanizm działa.

  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func main() {  
  6.     var x = 50
  7.     if x < 10 {        
  8.         fmt.Println("x jest mniejsze niż 10")  
  9.     } else {      
  10.         fmt.Println("x jest większe lub równe 10")
  11.     }
  12. }
  13.  
Switch w Go też jest obsługiwany i również stanowi instrukcję warunkową. Działa to taka że instrukcje Switch "oceniają" wyrażenie, a wynik jest porównywany z zestawem dostępnych wartości (przypadków). Po znalezieniu dopasowania wykonywane są instrukcje związane z tym dopasowaniem. Jeśli nie zostanie znalezione żadne dopasowanie, nic nie zostanie wykonane.
  1.  
  2. switch expression {
  3.     case value_1:
  4.         statements_1
  5.     case value_2:
  6.         statements_2
  7.     case value_n:
  8.         statements_n
  9.     default:
  10.         statements_default
  11.     }
  12.  
uffff jak to działa chyba wiecie , ale postaramy się zobrazować takim banalnym przykładem:
  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func main() {  
  6.     x,y := 2,1
  7.     switch x+y {
  8.     case 1:
  9.         fmt.Println("Wartość sumy to 1")
  10.     case 2:
  11.         fmt.Println("Wartość sumy to 2")
  12.     case 3:
  13.         fmt.Println("Wartość sumy to 3")
  14.     default:
  15.         fmt.Println("A co to ja kalkulator ??")
  16.     }
  17. }
  18.  
  19.  

jak się wiec domyślacie zapewne wynikiem tego programu będzie : Wartość sumy to 3
Dlatego że wyrażenie switch wyliczy sobie sumę zmiennych x+y i dopasuje 3 jako dopasowanie , a co za tym idzie wykona w tej sekcji zawarte
instrukcje. Jeśli chcecie się pobawić to zmieńcie wartość np y na 2 i zobaczycie że tym razem nie zostanie nic dopasowane , zatem zostaną wykonane instrukcje w sekcji default.

Później polecimy dalej z tablicami , na tą chwilę to wystarczy żeby się nam foreste nie przeciążył. :0
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Re: Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

---> Tablice w Go

Array reprezentuje tablicę o stałym rozmiarze jako nazwaną sekwencję nazwanych elementów tego samego typu. Oznacza to że nie możemy
mieć tablicy, która bedzie zawierać zarówno wartości liczbowe jak i znaki, dodatkowo nie możemy zmienić rozmiar tablicy w programie po
zdefiniowaniu jej rozmiaru. Co za tym idzie deklarujemy tablice następująco:
  1.  
  2. var <nazwa tablicy> <rozmiar> <typ>
  3.  
I teraz logicznym jest że przypisujemy wartość do danego elementu :
  1.  
  2. nazwa tablicy  <index> = wartość
  3.  
Jako ze indexy tablicy zaczynają się od zero (0) do -1 możemy sobie przypisać wartości do wielu indexów na raz podczas jej deklaracji:
  1.  
  2. nazwa_tablicy := [rozmiar] typ {wartość_0,wartość_1,,wartość_rozmiar-1}
  3.  
Ale możemy też zignorować parametr rozmiar podczas deklarowania tablicy z wartościami, zamieniając rozmiar na … ,
a kompilator znajdzie długość z liczby wartości czyli deklarujemy tak:
  1.  
  2. nazwa_tablicy := [] typ {wartość_0,wartość_1,,wartość_rozmiar-1}
  3.  
Długość tablicy mozemy też sobie znaleźć za pomocą :
  1.  
  2. len <nazwa tablicy>
  3.  
Jest to dość wygodne jak się okazuje i wydaje się byc przyjazne w uzytkowaniu podczas pisania obszernych programów, chyba doskonale zobrazuje
to przykładowy programik pokazujący użycie tablic. Moze nic odkrywczego, ale obrazuje całkiem fajnie :
  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func main() {  
  6.     var liczby [3] string   // deklarujemy tablicę stringów o 3ch elementach
  7.     liczby[0] = "Jeden"  // przypisujemy wartości
  8.     liczby[1] = "Dwa"
  9.     licby[2] = "Trzy"
  10.     fmt.Println(liczby[1])   // wypisujemy wartość indexu 1 tablicy
  11.     fmt.Println(len(liczby))  // wypisujemy rozmiar tablicy
  12.     fmt.Println(liczby)  // wypisujemy całą zawartość tablicy
  13.  
  14.     tablica1 := [...] int {1,2,3,4,5} // tworzymy tablice wartości liczbowych i przypisujemy wartości, rozmiar określi się sam po ilości elementów
  15.     fmt.Println(tablica1)
  16.     fmt.Println(len(tablica1))
  17.  
  18. }
  19.  
Prawda że łatwe do zapamiętania i w użyciu. Tak właśnie Go wspiera łatwość uczenia się programowania w tym języku. Wiele rzeczy jest zautomatyzowanych i wiele dzieje się domyślnie samo. Co zdecydowanie ułatwia pisanie programów i przyspiesza prace gdyż nad wieloma
rzeczami nie musimy się zastanawiać czy tez nimi przejmować. Żeby sie jednak rozprawić z tablicami raz na zawsze warto wspomnieć
teraz iż Go dysponuje wbudowanymi narzędziami(funkcjami) do wykonywania operacji na tablicach jak slice i append.

To całkiem ciekawe gdyż "Wycinek" to część lub hmm.. segment tablicy. Może nie do końca jasno to rozumiem
jeszcze, a le może to być też widok, lub częściowy widok podstawowej tablicy, którą jasno wskazuje. Do tych "wycinków" lub elementów ,
możemy uzyskać dostęp używając nazwy i indexu. Tak jakbyśmy chcieli się dostać do wartości z tablicy, ale choć nie możemy zmienić rozmiaru tablicy tu uwaga @ możemy zmienić rozmiar takiego wycinka.

W zasadzie zawartość wycinka to w rzeczywistości wskaźnik do elementów tablicy, co dla nas oznacza , że jeśli zmienimy dowolny element w wycinku będzie to miało również wpływ na element tablicy. Wydaje się to trochę skomplikowane jednak obrazując kodem okazuje się,
że jest bardzo proste. Tak wiec tworzenie takiego "wycinka" wygląda tak:
  1.  
  2. var nazwa_wycinka [] typ = nazwa_tablicy[początek:koniec]
  3.  
Linijak taka stworzy nam wycinek o nazwie nazwa_wycinka z tablicy (nazwa_tablicy) z elementami o indexach od początku do końca. Żeby to ogarnąć mógł foreste zobaczmy taki banalny przykład w kodzie:
  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func main() {  
  6.    
  7.     tablica := [5] string {"jeden", "dwa", "trzy", "cztery", "pieć"}
  8.     fmt.Println("Stworzyliśmy tablicę:",tablica)
  9.  
  10.     var wycinek [] string = tablica[1:4]  // wycinamy plasterek
  11.     fmt.Println("Wycinek :",wycinek)
  12.  
  13.     wycinek[0]="zmieniony" // zmieniamy dane wycinka
  14.     fmt.Println("Wycinek zmodyfikowany:",wycinek)
  15.     fmt.Println("Tablica po zmodyfikowaniu wycinka:",tablica)
  16. }
  17.  
  18.  

jak nie trudno się domyślić wynik programu będzie następujący:

  1.  
  2. Stworzyliśmy tablicę: [jeden dwa trzy cztery pięć]
  3. Wycinek: [dwa trzy cztery]
  4. Wycinek zmodyfikowany: [zmieniony trzy cztery]
  5. Tablica po zmodyfikowaniu wycinka: [jeden zmieniony trzy cztery pięć
  6.  
Teraz wygląda to jasno i przejrzyście prawda ??
Ba okazuje sie też że jest przydatne choćby gdy zajdzie konieczność zmodyfikowania tablicy w programie
podczas jego pracy. Ale warto też wiedzieć że Go oferuje nam funkcje takie jak len i append , które możemy zastosować na wycinkach.

len(nazwa_wycinka) -- zwraca nam długość wycinka
append(nazwa_wycinka, wartość1, wartość2) -- pozwala dołączyć wartości (wartość1 i 2) do istniejącego wycinka
append(nazwa_wycinka1, nazwa_wycinka2...) -- pozwoli nam dołączyć wycinek2 do tego pierwszego.

Prawda że zmyślny mechanizm :) Im dłużej się bawię GoLangiem tym bardziej mi się podoba. Powiem wam ze to bardzo przyjemny
jeżyk i warto go poznać co mogę stwierdzić juz na tym etapie. Pobawcie się funkcjami sami zobaczycie że to fajna sprawa i już na
początku widać szerokie zastosowania w pisanych aplikacjach ... ale to tylko wierzchołek góry lodowej , niebawem ruszymy w dalszą podróż ...
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Re: Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

---> Funkcje

Funkcja jak wiemy z innych języków programowania reprezentuje blok instrukcji, którego zadaniem jest wykonywanie określonych zadań. W Go również funkcjonujś funkcje gdyż nie jako bez nich żaden program obejść
się nie może. Funkcje ułatwiają i skracają kod zwłaszcza gdy z danych bloków instrukcji często korzystamy w kodzie głównym. Dzięki nim nie musimy pisać tych samych linijek kodu , a jedynie wywołujemy funkcję z określonymi parametrami i oczekujemy określonych wyników. W Go Deklaracja funkcji informuje nas o jej nazwie oraz typie zwracanych informacji jak również o parametrach wejściowych. Funkcję w Go deklarujemy następująco:
  1.  
  2. func nazwa_funkcji(typ_parametr_1, typ_n_parametru) typ_zwrotu {
  3.  
  4. // ciało funkcji (zestaw instrukcji)
  5.  
  6. }
  7.  
Oczywiście zarówno parametry jak i typy zwracane są całkowicie opcjonalne. Ponadto możemy zwracać wiele różnych wartości z funkcji co również ułatwia nam zadanie przy projektowaniu programu.
Co w sumie nic nowego nie wnosi względem innych języków, ale jest istotne na poziomie tworzenia kodu.

---> Defer

Kolejną ciekawą właściwością w języku Go są "Odroczenia" Mianowicie język Go posiada instrukcje Defer , która służy do "odroczenia" wykonania danej funkcji aż do momentu zakończenia funkcji w której zawarty jest defer.
Aby zrozumieć działanie skorzystać możemy z nieco usprawnionego programu Hello World.

  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func funkcja1() {  
  6.     fmt.Println("Witaj Wszechświecie")
  7. }
  8. func main() {  
  9.    
  10.     defer funkcja1()
  11.     fmt.Println("Witaj Świecie")
  12. }
  13.  

Widzicie, najpierw zostanie wyświetlony napis Witaj świecie , a dopiero po nim Witaj Wszechświecie.
Oczywiście to samo możemy uzyskać wywołując funkcję "funkcja1" po wyświetleniu napisu Witaj Świecie,
ale taka ciekawostka może się nam przydać podczas pisania kodu jak właśnie możliwość odroczenia wykonania
funkcji. To też nie wszystko istnieje jeszcze Stacking defer czyli można powiedzieć takie swoiste układanie list odroczeń. Wspomniany Stacking defer używa wielu wywołań defer. Jak to wytłumaczyć ... jak w kodzie użyjemy wielu odroczeń to Go samodzielnie wszystkie wywołania tych funkcji umieści na stosie, a potem je ze stosu wykona i tu warto wiedzieć że wykona je w kolejności tzw LIFO czyli Last in First Out .. co jak się domyślacie ostatnie wywołanie będzie pierwsze wykonane. Warto o tym pamiętać podczas używania odroczeń.
Bo można się zdziwić działaniem kodu:)

---> Wskaźniki

To takie można powiedzieć universum języka Go. W zasadzie pamiętacie jak pisałem że zmienna w Go wskazuje adres konkretnej wartości do niej przypisanej , a co za tym idzie w Go mogą występować zmienne nienazwane ?? No właśnie. I tu kluczowe są wskaźniki , a właściwie trzeba zacząć od operatora "&" bowiem właśnie on służy do uzyskania adresu zmiennej. Jak się sami przekonacie '&zmienna' wypisze nam adres pamięci gdzie znajduje się
wartosć przypisana do zmiennej "zmienna"
  1.  
  2. package main
  3. import "fmt"
  4.  
  5. func main() {
  6.     zmienna := 256
  7.     fmt.Println("Adres:",&zmienna)
  8.     fmt.Println("Wartość:",zmienna)
  9. }
  10.  
Czyli w moim wypadku wynikiem powyższego kodu jest :

Adres: 0xc000020098
Wartość: 256


I tu warto wiedzieć że pojawia się nam zmienna wskaźnikowa , która przechowuje adres innej zmiennej. Taki wskaźnik możemy sobie zdefiniować poleceniem. właśnie (*) informuje nas i kompilator że ta konkretna zmienna jest wskaźnikiem.

var nazwa_zmiennej *typ

Pobawcie się trochę , wskaźniki w Go są bardziej przyjazne od znanych nam w C czy w C++ , tu jest wiele rzeczy jakby bardziej ogarniętych i uproszczonych , nie bez powodu reklamują Go jako prostszy od pascala i basica ale wydajny jak C i C++ choć z tą wydajnością .... ale o tym później
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Re: Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

Już niewiele nam zostało do opisania w tym skromnym samouczku, Go jest tak prosty że tematów za wiele nie ma
do poruszenia zwłaszcza gdy czytelnikiem jest ktoś kto otarł się o inne języki. Podobieństw jest sporo , zasady
w sumie nie odbiegają od innych. Jak to wspomniał kol. Elvis w rozmowie na czacie --" Go jest jak C czytane od prawej do lewej " wiecie jak się zastanowić to coś w tym jest i faktycznie stwierdzenie doskonale t pasuje.
Ale zanim zakończymy zostało kilka tematów ...

---> Struktury

To taki zdefiniowany przez użytkownika Typ danych. Zawiera on w sobie jeden element tego samego lub innego
typu tak w sumie niejako dodatkowo. Użycie struktury wiec jest dwuetapowym procesem, na który składa się
-- utworzenie typu struktury
-- utworzenie zmiennych tego typu do przechowywania wartości
Struktura przydaje się nam w zasadzie tylko w momencie gdy chcemy razem przechować powiązane dane. Przykładowo mamy pudełko podzespołów które posiadają swoją nazwę , rodzaj i ilość ..
Oczywiście problem segregacji można na wiele sposobów rozwiązać a przynajmniej 2 się same nasuwają.

--->> 1
Utworzyć możemy 3 tablice -- jedna będzie przechowywać nazwę , druga rodzaj a trzecia ilość ....

--->>2
Zadeklarujemy typ struktury z 3ma polami - Nazwa, rodzaj , ilość . Teraz utworzymy tablicę dla tego typu struktury
w której każdy element jest obiektem struktury, posiadającym nazwę , rodzaj i ilość.

Jak juz widzicie na pierwszy rzut oka 1 rozwiązanie z tablicami nie do końca jest optymalne , nawet wydajność tego rozwiązania stoi pod wielkim znakiem zapytania i jak już zauważyliście w takim scenariuszu struktura będzie zdecydowanie wygodniejsza w użyciu jak i w dostępie do przechowywanych w niej danych.

Strukturę w Go deklarujemy:
  1.  
  2. type nazwa_struktury struct {
  3.    zmienna1 typ
  4.    zmienna2 typ
  5.    zmienna3 typ
  6. }
  7.  
Wiec w przypadku naszego wymyślonego problemu struktura będzie wyglądać następująco :
  1.  
  2. type podzespoły struct {
  3.         nazwa string
  4.         typ string
  5.         ilosc string
  6. }
  7.  
wiec skoro mamy już utworzony typ nie pozostaje nam, nic innego jak przygotowanie zmiennych o typie podzespoły co tez uczynimy w taki sposób: var nazwa_zmiennej nazwa_struktury

Co powinno wyglądać tak:
  1.  
  2. var element1 podzespoły
  3.  
a co za tym idzie łatwo ustawimy sobie parametry wartości naszej że tak powiem komórki element1
  1.  
  2. element1.nazwa = "BC107"
  3.               element1.typ = "NPN"
  4.               element1.ilosc = "45"
  5.  
Prawda że banalnie proste jest używanie struktur ? Mnie też się to podoba. Oczywiście można całość uprościć,
np tak :
  1.  
  2. element2 := podzespoły{"BC108", "NPN", "11"}
  3. element3 := podzespoły{"BC211", "NPN", "9"}
  4.  
Należy jednak w tym zapisie zwrócić uwagę na kolejność wartości gdyż są one kolejno przypisywane do właściwych pól w strukturze.

Ale jak zwykle najprościej będzie zobrazować wszystko małym kodem, który nam wizualnie pokaże o co chodzi
  1.  
  2. package main
  3.  
  4. import "fmt"
  5.  
  6. type podzespoły struct {
  7.     nazwa string
  8.     typ   string
  9.     ilość int
  10. }
  11.  
  12. func wyświetl(podz podzespoły) {
  13.     fmt.Println(podz.nazwa, podz.typ)
  14. }
  15.  
  16. func main() {
  17.  
  18.     var element1 podzespoły
  19.  
  20.     element1.nazwa = "BC107"
  21.     element1.typ = "NPN"
  22.     element1.ilość = 45
  23.  
  24.     element2 := podzespoły{"BC108", "NPN", 11}
  25.     element3 := podzespoły{"BC211", "NPN", 9}
  26.  
  27.     wyświetl(element1)
  28.     wyświetl(element2)
  29.     wyświetl(element3)
  30. }
  31.  
Widzicie ?? efektem działania programy będzie oczywiście :

BC107 NPN
BC108 NPN
BC211 NPN


Jak wiec widać dostęp do struktury i zawartych tam danych jest banalnie proste i przyjemne , ale chciałbym
zwrócić waszą uwagę na jeszcze jedną ciekawostkę w języku Go. Zauważyliście, że w nazwach używam sobie polskich znaków diakrytycznych jak "śćńł" itd ... zamiast pisać wyswietl --> wyświetl ??
Nie jest to błędem podczas pisania tekstu. Porostu można sobie w Go bezproblemowo pisać po polsku.
A to dlatego ze Go obsługuje unicode co pozwala używać dowolnego języka zarówno w nazwach funkcji jak i zmiennych nie tylko jak w innych językach w komentarzach na co się i tak fochają ....
Uff... fajne prawda , i tym sposobem ogarnięte zostały struktury , choć strasznie wyglądały :)

Obrazek
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Re: Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

Pamiętacie jak pisałem że Go choć jest językiem obiektowym nie posiada czegoś takiego jak klasy.
Właściwie to cała sytuacja ma głębsze dno bowiem zasadniczo Go nie do końca jest językiem zorientowanym
obiektowo. Więc w takim przypadku pojęcie klasy jak w C++ jest zbędne , ale nie oznacza to, że w Go nie można
pisać programów obiektowo bynajmniej oczywiście że można i czasem wręcz trzeba. W Go istnieją metody,
które poniekąd pozwalają poczuć tą samą adrenalinkę jak podczas pisania programów obiektowych. Inaczej mówiąc
Metoda to taka odmienna funkcja, która posiada argument odbiorcy. Zasadniczo znajduje się ona pomiędzy słowem kluczowym func,
a nazwą metody. Wiem ze trochę to zawiłe , ale tak się tylko wydaje. Zobrazować można to prostą w stylu Goskładnią metody:
  1.  
  2. func (zmiena  typ) nazwaMetody (parametr1  typ) {
  3. }
  4.  
  5.  
Może inaczej, weźmy nasz program ze strukturą i lekko go zmodyfikujmy tak by nie używał funkcji a metodę:
  1.  
  2. package main
  3.  
  4. import "fmt"
  5.  
  6. type podzespoły struct {
  7.     nazwa string
  8.     typ   string
  9.     ilość int
  10. }
  11.  
  12. func (pok podzespoły) wyswietl() {
  13.     fmt.Println(pok.nazwa, pok.typ)
  14. }
  15.  
  16. //func wyświetl(podz podzespoły) {
  17. //  fmt.Println(podz.nazwa, podz.typ)
  18. //}
  19.  
  20. func main() {
  21.     var element1 podzespoły
  22.  
  23.     element1.nazwa = "BC107"
  24.     element1.typ = "NPN"
  25.     element1.ilość = 45
  26.  
  27.     element2 := podzespoły{"BC108", "NPN", 11}
  28.     element3 := podzespoły{"BC211", "NPN", 9}
  29.  
  30.     //wyświetl(element1)
  31.     //wyświetl(element2)
  32.     //wyświetl(element3)
  33.  
  34.         element1.wyswietl()
  35.     element2.wyswietl()
  36.     element3.wyswietl()
  37.  
  38.  
Widzicie teraz używamy metody (a'la klasa) z odbiornikiem podzespoły. Jak widzicie wiele się nie zmieniło, starą
funkcję zostawiłem do zobrazowania w komentarzach. Tym sposobem piszemy jak z użyciem klas gdzie wywołanie mamy w formie nazwaObiektu.nazwaFunkcji() , a poprzednio było nazwaFunkcji(nazwaObiektu)
Prawda że jest to proste i jakże obiektowe :)

Pisałem również że go wpiera współbieżność wykonywania zadań. W sumie to takie trochę bardzo teoretyczne stwierdzenie i dość ogólnikowe bo widzicie w Go działa to wszystko nieco inaczej i zdecydowanie jest inne od koncepcji równoległości. Bowiem w według koncepcji równoległości zadanie jest dzielone na małe podzadania i wykonywane równolegle zaś współbieżność oznacza że wiele zadań jest wykonywanych równocześnie czyli taki multitasking ja w AmigaOS gdzie wiele różnych zadań działało jednocześnie. W Go do tego celu służą Gorutyny i Kanały.
(tak wydaje się że nas to wpuści w niezły kanał , ale naprawdę fajnie to działa)

O co wiec tu chodzi ?? Zacznijmy od Gorutyny ... to taka specyficzna funkcja, która może sobie działać jednocześnie z innymi funkcjami.
Jak to rozumieć spyta foreste , ano to proste ... Wyobraźmy sobie wywołanie funkcji w programie:

- w jakiejś funkcji wywołujemy inną funkcję
-- ona sobie działa program sobie czeka
--- po jej zakończeniu program wraca do miejsca wywołania
---- program kontynuuje wykonywanie instrukcji

Tymczasem w trakcie wykonywania Gorutyny funkcja która ją wywołała nie czeka aż się ona wykona , tylko będzie kontynuować swoją pracę z kolejnymi instrukcjami co więcej program główny zakończy nawet swoją pracę po wykonaniu swoich instrukcji nie czekając aż zakończy się wykonanie Gorutyny. Dodatkowo w kodzie można zaszyć wiele Gorutyn. Gorutyne wywołujemy słowem kluczowym GO po którym
podajemy nazwę funkcji:
  1.  
  2. go dodaj(a,b)
  3.  
Aby to zobaczyć zobrazujemy to prostym kodem:
  1.  
  2. package main
  3. import "fmt"
  4. import "time"
  5.    
  6. func pokaż() {
  7.     for i:=0; i<5; i++ {
  8.         time.Sleep(1 * time.Second)
  9.         fmt.Println("Pokazuję !")
  10.     }
  11. }
  12.  
  13. func main() {
  14.    
  15.     go pokaż()
  16.     for i:=0; i<5; i++ {
  17.         time.Sleep(2 * time.Second)
  18.         fmt.Println("Nie pokazuję")
  19.     }
  20. }
  21.  
Widzicie ... obie funkcje się niejako na siebie nakładają z powodu właśnie współbieżnego wykonania, jeśli zaś usuniemy opóźnienie zobaczymy,
że program zakończy działanie a funkcja pakaż() się nie wykona...
  1.  
  2. package main
  3. import "fmt"
  4.    
  5. func pokaż() {
  6.     for i:=0; i<5; i++ {
  7.         fmt.Println("Pokazuję !")
  8.     }
  9. }
  10.  
  11. func main() {
  12.    
  13.     go pokaż()
  14.     for i:=0; i<5; i++ {
  15.         fmt.Println("Nie pokazuję")
  16.     }
  17. }
  18.  
Stanie się tak dlatego że nasz program nie będzie czekał na wykonanie funkcji pokaż i wykona się dalej.
Jak sami widzicie już zapewne daje to całkiem wiele ciekawych możliwości.

---> Kanały

Co ciekawe wyżej wam pokazałem że program nie będzie czekał na wykonanie gorutyny, ale też tak nie do końca jeśli użyjemy kanału program główny będzie zmuszony czekać. Dlaczego ? Ano dlatego że jeśli Gorutyna przesyła dane do kanału program musi poczekać na instrukcję odbierającą dane z niego aż on takowe otrzyma. Hmm... co to jest ten kanał ??
Ogólnie rzecz mówiąc kanał jest sposobem komunikacji miedzy funkcjami. Taki komunikator gdzie jedna funkcja
umieszcza dane , a inna je odczytuje. Kanał deklarujemy poleceniem:
  1.  
  2. nazwaKanału := make(chan typdanych)
  3.  
Gdzie może to wyglądać np tak:
  1.  
  2. kanał1 := make(chan int)
  3.  
I teraz możemy wysłać sobie dane do kanału1 tak:
  1.  
  2. kanał1 <- c
  3.  
a odbieramy je tak:
  1.  
  2. a := <- kanał1
  3.  
Prawda że proste i wygodne ? By to ogarnąć zmodyfikujcie sobie program który nie czeka na gorutyne tak by korzystał z kanału a zobaczycie co się stanie. Najlepiej się właśnie rozumie takie dylematy jak się rozwiązuje jakiś problem :)

Uff... Co ciekawe można kanał zamknąć zastanawiam się na razie nad sensem otwierania i zamykania kanału
a właściwie nad wykorzystaniem potencjału. Chodzi o to że funkcja nadawcza może poinformować inne funkcje że żadnych danych nie wyśle, robi to polecenie:
  1.  
  2. close(nazwaKanału)
  3.  
Co nam to daje , no może być to użyteczne gdyż funkcja odbierająca może sprawdzić czy dany kanał
jest zamknięty za pomocą dodatkowej zmiennej (i tak właśnie rośnie kod w siłę objętości)
  1.  
  2. nazwaZmiennej, status := <- nazwaKanału
  3.  
Status zwraca True jeśli otrzymamy jakieś dane z kanału, a False jeśli kanał jest zamknięty.
Jak wiec widzicie kanały mają potencjał, można mim przesyłać dane z funkcji do funkcji miedzy wieloma funkcjami i miedzy gorutynami.
Dzięki czemu przekazywanie wyników funkcji jest wygodne i szybkie. Co więcej kanałów możemy mieć też wiele i możemy wysyłać wiele
danych. W tym momencie powiecie, że robi się młyn i zamieszanie ... poniekąd ale można nad tym zapanować również prosto używając
pewnej funkcji o której napiszę następnym razem dając wam czas do przyswojenia sobie materiału.
Awatar użytkownika
SunRiver
Użytkownik
Posty: 1511
Rejestracja: 08 paź 2017, 11:27
Lokalizacja: Festung Oppeln
Kontakt:

Re: Go... Go... na tańce .. Go , Go , Go ....

Post autor: SunRiver »

Nieuchronnie zbliżamy się do końca tutorialu. Jak poprzednio wspomniałem nad kanałami i wykonaniem wielu rzeczy można zapanować instrukcją , którą to jest :

---> Select

Jest ona bardzo podobna do Switch , gdyż również używa case i działa dokładnie tak samo tylko na właśnie kanałach. Możemy ją sobie wyobrazić jako panel z przełącznikami, które je przełączają. Ma to sens jeśli pamiętamy
że każda operacja na kanale będzie próbą jego odczytania a właściwie danych tam zawartych. Czyli gdy jakieś zadanie zostanie wykonane, a kanał będzie odczytany wykonana zostanie instrukcja związana z tym zadaniem. Jeśli
zaś mamy wiele zadań i wiele jest gotowych to wybór będzie dotyczył jednej losowej co jak sobie wyobrażacie
będzie nieco nieoczekiwane i tu właśnie porządek robi w papierach ów select. Pozwala on mieć też opcję domyślną , która będzie wykonana gdy żadne z zadań nie będzie gotowa. Wydaje się to zawiłe, wiec najlepiej zobrazować prostym kodem ze studia przypadku.

Nasz program prowadzi poszukiwania Atmeg w szufladzie foresta:
  1.  
  2. package main
  3.  
  4. import "fmt"
  5. import "time"
  6.  
  7. func element1(kanal chan string) {
  8.     time.Sleep(2 * time.Second)
  9.     kanal <- "Jest Atmega128"
  10. }
  11.  
  12. func element2(kanal chan string) {
  13.     time.Sleep(1 * time.Second)
  14.     kanal <- "Jest Atmega328"
  15. }
  16.  
  17. func main() {
  18.     kanal1 := make(chan string)
  19.     kanal2 := make(chan string)
  20.  
  21.     fmt.Println("Szukamy Atmegi w szufladzie foresta:")
  22.     time.Sleep(3 * time.Second)
  23.  
  24.     go element1(kanal1)
  25.     go element2(kanal2)
  26.  
  27.     select {
  28.     case a := <-kanal1:
  29.         fmt.Println(a)
  30.     case b := <-kanal2:
  31.         fmt.Println(b)
  32.     }
  33. }
Jak widzicie program odnalazł Atmegę328 czyli wykonał się przypadek 2 dlatego że jak wspomniałem
wybór jest losowy , a że kanał2 drugi umieszcza dane po 1 sekundzie opóźnienia właśnie on będzie wykonany.
  1.  
  2. package main
  3.  
  4. import "fmt"
  5. import "time"
  6.  
  7. func element1(kanal chan string) {
  8.     time.Sleep(2 * time.Second)
  9.     kanal <- "Atmega128"
  10. }
  11.  
  12. func element2(kanal chan string) {
  13.     time.Sleep(1 * time.Second)
  14.     kanal <- "Atmega328"
  15. }
  16.  
  17. func main() {
  18.     kanal1 := make(chan string)
  19.     kanal2 := make(chan string)
  20.  
  21.     fmt.Println("Szukamy Atmegi w szufladzie foresta:")
  22.     time.Sleep(3 * time.Second)
  23.  
  24.     go element1(kanal1)
  25.     go element2(kanal2)
  26.  
  27.     select {
  28.     case a := <-kanal1:
  29.         fmt.Println(a)
  30.     case b := <-kanal2:
  31.         fmt.Println(b)
  32.     default:
  33.         fmt.Println("Nie ma tutaj żadnej Atmegi !!")
  34.     }
  35. }
  36.  
Program ten wyświetli " Nie ma tutaj żadnej Atmegi !" Dzieje się tak dlatego, że żadne z naszych zadań nie zostało wykonane. Tylko dlatego, ze oba zadania wysyłają informacje z opóźnieniem 2 i 1 sekundowym, a program nie mając nic do odczytania wykonał akcje domyślną nie czekając na znalezienie jakiejś Atmegi i tym samym w szufladzie foresta nie ma żadnej atmegi ... W sumie powinny to być kwarce bo je zawsze foreste gubi :) Tu było prosto bowiem manipulowałem sobie opóźnieniem wiec nie jako miałem wpływ na to zadanie które się wykona, ale w programie większym często o tym będzie decydował przypadek czy konkretny czas wykonania danego bloku instrukcji. Ale przykład taki forestański doskonale obrazuje działanie Select.

Ostatnią rzeczą jaką opiszę w tym tutorialu to wzajemne wykluczenie czyli:

---> Mutex

To ogólnie rzecz biorąc taka skrócona forma wzajemnego wykluczenia, używa się go w wypadku, w którym nie chcemy by wiele podprogramów uzyskało dostęp do danego zasobu w tym samym czasie. Mutex używa metod Zablokuj/Odblokuj. Dostępny jest w pakiecie sync, który musimy zaimportować w naszym programie by można było go użyć. Najprościej zrozumieć jak działa na przykładzie jakiegoś drętwego kodu, który da się ogarnąć nawet forestowi ... ( coś dziś dużo foresta, ale to stałym bywalcom forum obrazuje stopień skomplikowania).
No dobrze ... Więc foreste ma szafkę z 3ma szufladami w której chowa kwarce, a potem nam mówi ze zgubił.
No to zobaczmy w której ile ich schował i ile mu się najwięcej zmieściło:
  1.  
  2. package main
  3.  
  4. import "fmt"
  5. import "time"
  6. import "strconv"
  7. import "math/rand"
  8.  
  9. var ilosc = 0
  10.  
  11. func szafka(x int) {
  12.  
  13.     for q := 0; q < 10; q++ {
  14.         time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
  15.         iloscKwarców := ilosc
  16.         iloscKwarców++
  17.         time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
  18.         ilosc = iloscKwarców
  19.     }
  20.     fmt.Println("W szufladzie ="+strconv.Itoa(x)+" Jest :", strconv.Itoa(ilosc), "Kwarców")
  21. }
  22.  
  23. func main() {
  24.  
  25.     for szuflada := 1; szuflada < 4; szuflada++ {
  26.         go szafka(szuflada)
  27.     }
  28.  
  29.     //delay to wait for the routines to complete
  30.     time.Sleep(25 * time.Second)
  31.     fmt.Println("Najwięcej w szufladzie zmieścił", ilosc, "Kwarców")
  32. }
  33.  
  34.  
  35.  

Co tu się wydarzyło .... Tak naprawdę liczymy nie kwarce foresta , a ile razy się wykona pętla. Spodziewamy się 10 przebiegów , a wywołujemy liczenie 3 razy więc na koniec powinniśmy otrzymać wynik 30, prawda ?? Tymczasem to się nie zgadza wynik jest inny i nie do końca zgodny
z naszym oczekiwaniem ... Dzieje się tak dlatego, że 3 uruchomienia wywołują 3 gorutyny z których każda próbuje zwiększać ilość przebiegów
pętli w zmiennej ilosc, a żadna nie czeka gdy inna korzysta z tej zmiennej i nadal zapisuje wartość . Co powoduje błędne wykonanie,
które obrazuje powyższy kod ... gdzie można ująć prosto każde wykonanie nie będzie zgodne z oczekiwaniem , a całkowicie przypadkowe.
Takiemu przypadkowi możemy zapobiec jeśli użyjemy Mutexu. Spowoduje to, że gdy jedno wywołanie będzie używać zmiennej ilość inne będą czekać aż proces zakończy pracę i zwolni zmienną z wartością końcową.
Zobaczcie:
  1.  
  2. package main
  3.  
  4. import "fmt"
  5. import "time"
  6. import "strconv"
  7. import "math/rand"
  8. import "sync"
  9.  
  10. var wyklucz sync.Mutex
  11.  
  12. var ilosc = 0
  13.  
  14. func szafka(x int) {
  15.  
  16.     for q := 0; q < 10; q++ {
  17.         time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
  18.         //---------------------------------
  19.         wyklucz.Lock()
  20.         iloscKwarców := ilosc
  21.         iloscKwarców++
  22.         time.Sleep(time.Duration(rand.Int31n(2)) * time.Second)
  23.         ilosc = iloscKwarców
  24.         wyklucz.Unlock()
  25.         //-----------------------------------
  26.     }
  27.     fmt.Println("W szufladzie ="+strconv.Itoa(x)+" Jest :", strconv.Itoa(ilosc), "Kwarców")
  28. }
  29.  
  30. func main() {
  31.  
  32.     for szuflada := 1; szuflada < 4; szuflada++ {
  33.         go szafka(szuflada)
  34.     }
  35.  
  36.     //delay to wait for the routines to complete
  37.     time.Sleep(25 * time.Second)
  38.     fmt.Println("Najwięcej w szufladzie zmieścił", ilosc, "Kwarców")
  39. }
  40.  
  41.  
Jak widać wynik końcowy jest zgodny z oczekiwaniem i wynosi 30. Właśnie dlatego, że wszystkie instrukcje zwiększania zmiennej i jej zerowania
są wykonane w mutexie. Dlatego końcowy wynik jest zgodny z naszym oczekiwaniem które wynika z parametrów pętli.
Teraz sobie wyobraźcie taki scenariusz gdzie jakieś ważne wyliczenie jest zależne od wartości wyliczonej w podobny sposób i nie mamy Mutexa ... :)


I to by było na tyle z tych wszystkich istotniejszych rzeczy które znajdziemy w Go. Na tą chwilę uczę się nadal, Pisząc coraz większe programy, Dużą pomocą są książki o których wspomniałem wcześniej choć jest w nich sporo literówek i błędów. Niemniej pomagają poznać i opanować podstawy
programowania w tym pociesznym i ciekawym języku. Oczywiście ten tutorial nie wyczerpuje tematu to zaledwie zalążek początku, ale pozwalający
na w miarę swobodne pisanie programów. Oczywiście jak się nieco doszkolę opisze kolejne ciekawostki i dziwadła. Tymczasem zapraszam
do komentowania i zadawania pytań, na które będę się starał odpowiedzieć najbardziej rzeczowo jak będę umiał i cicho liczę na pomoc kolegi Elvisa.

Możliwe jest też że będę kontynuował w stylu jaki znacie z moich opisów C# choćby gdzie po prostu pokazuję jak w dany sposób osiągnąć jakiś
konkretny cel. Co też swego rodzaju jest dobra metodą takie studiowanie przypadku na konkretnym kodzie tworzącym konkretny cel.
ODPOWIEDZ

Wróć do „GoLang”