Урок за Golang: Научете езика за програмиране Go за начинаещи
Какво е Go?
Go (известен също като Golang) е език за програмиране с отворен код, разработен от Google. Това е статично типизиран компилиран език. Go поддържа едновременно програмиране, т.е. позволява стартиране на множество процеси едновременно. Това се постига с помощта на канали, goroutines и т.н. Go Language има събиране на отпадъци, което само управлява паметта и позволява отложеното изпълнение на функциите.
Ще научим всички основи на Golang в този урок за езика Learn Go.
Как да изтеглите и инсталирате GO
Стъпка 1) Отиди https://golang.org/dl/. Изтеглете двоичния файл за вашата операционна система.
Стъпка 2) Double щракнете върху инсталатора и щракнете върху Изпълнение.
Стъпка 3) Кликнете върху Напред
Стъпка 4) Изберете папката за инсталиране и щракнете върху Напред.
Стъпка 5) Щракнете върху Готово, след като инсталацията приключи.
Стъпка 6) След като инсталацията приключи, можете да я проверите, като отворите терминала и напишете
go version
Това ще покаже инсталираната версия на go
Вашата първа програма Go – Go Hello World!
Създайте папка, наречена studyGo. В този езиков урок за Go ще създадем нашите go програми в тази папка. Go файловете се създават с разширението .отивай. Можете да стартирате Go програми, като използвате синтаксиса
go run <filename>
Създайте файл с име first.go и добавете кода по-долу в него и запазете
package main import ("fmt") func main() { fmt.Println("Hello World! This is my first Go program\n") }
Отидете до тази папка във вашия терминал. Стартирайте програмата с помощта на командата
бягай първи
Можете да видите изходния печат
Hello World! This is my first Go program
Сега нека обсъдим горната програма.
основният пакет – всяка програма Go Language трябва да започва с име на пакет. Go ни позволява да използваме пакети в други go програми и следователно поддържа повторно използване на кода. Изпълнението на Go програма започва с кода в пакета с име main.
import fmt – импортира пакета fmt. Този пакет изпълнява I/O функциите.
func main() – Това е функцията, от която започва изпълнението на програмата. Основната функция винаги трябва да се поставя в основния пакет. Под main() можете да напишете кода вътре {}.
fmt.Println – Това ще отпечата текста на екрана чрез функцията Println на fmt.
Забележка: В разделите по-долу на този урок за Go, когато споменавате изпълнение/изпълнение на кода, това означава да запишете кода във файл с разширение .go и да го стартирате, като използвате синтаксиса
go run <filename>
Типове данни
Типовете (типовете данни) представляват типа на стойността, съхранена в променлива, типа на стойността, която функцията връща и т.н.
Има три основни типа в Go Language
Числови типове – Представяне на числови стойности, които включват цели числа, стойности с плаваща запетая и комплексни стойности. Различни числови типове са:
int8 – 8-битови цели числа със знак.
int16 – 16-битови цели числа със знак.
int32 – 32-битови цели числа със знак.
int64 – 64-битови цели числа със знак.
uint8 – 8-битови цели числа без знак.
uint16 – 16-битови цели числа без знак.
uint32 – 32-битови цели числа без знак.
uint64 – 64-битови цели числа без знак.
float32 – 32-битови числа с плаваща запетая.
float64 – 64-битови числа с плаваща запетая.
complex64 – има float32 реални и имагинерни части.
complex128 – има float32 реални и имагинерни части.
Типове низове – Представлява поредица от байтове (знаци). Можете да извършвате различни операции върху низове като конкатенация на низове, извличане на подниз и т.н
Булеви типове – Представлява 2 стойности, истина или невярно.
Интерфейс Golang
Интерфейс Golang е колекция от сигнатури на метод, използвани от тип за прилагане на поведението на обекти. Основната цел на интерфейса на Golang е да предостави сигнатури на метод с имена, аргументи и типове връщане. От тип зависи да декларира и приложи метода. Интерфейс в Golang може да бъде деклариран с помощта на ключовата дума „интерфейс“.
Променливи
Променливите сочат към място в паметта, което съхранява някакъв вид стойност. Параметърът тип (в синтаксиса по-долу) представлява типа стойност, която може да се съхранява в местоположението на паметта.
Променливата може да бъде декларирана с помощта на синтаксиса
var <variable_name> <type>
След като декларирате променлива от даден тип, можете да присвоите променливата на всяка стойност от този тип.
Можете също така да дадете начална стойност на променлива по време на самата декларация, като използвате
var <variable_name> <type> = <value>
Ако декларирате променливата с първоначална стойност, Go и изведете типа на променливата от типа на присвоената стойност. Така че можете да пропуснете типа по време на декларацията, като използвате синтаксиса
var <variable_name> = <value>
Освен това можете да декларирате множество променливи със синтаксиса
var <variable_name1>, <variable_name2> = <value1>, <value2>
Програмата по-долу в този урок за Go има някои примери на Golang за декларации на променливи
package main import "fmt" func main() { //declaring a integer variable x var x int x=3 //assigning x the value 3 fmt.Println("x:", x) //prints 3 //declaring a integer variable y with value 20 in a single statement and prints it var y int=20 fmt.Println("y:", y) //declaring a variable z with value 50 and prints it //Here type int is not explicitly mentioned var z=50 fmt.Println("z:", z) //Multiple variables are assigned in single line- i with an integer and j with a string var i, j = 100,"hello" fmt.Println("i and j:", i,j) }
Резултатът ще бъде
x: 3 y: 20 z: 50 i and j: 100 hello
Go Language също предоставя лесен начин за деклариране на променливите със стойност чрез пропускане на ключовата дума var
<variable_name> := <value>
Имайте предвид, че сте използвали := вместо =. Не можете да използвате := само за да присвоите стойност на променлива, която вече е декларирана. := се използва за деклариране и присвояване на стойност.
Създайте файл, наречен assign.go, със следния код
package main import ("fmt") func main() { a := 20 fmt.Println(a) //gives error since a is already declared a := 30 fmt.Println(a) }
Изпълнете go run assign.go за да видите резултата като
./assign.go:7:4: no new variables on left side of :=
Променливите, декларирани без първоначална стойност, ще имат 0 за числови типове, false за Boolean и празен низ за низове
константи
Постоянните променливи са тези променливи, чиято стойност не може да бъде променена, след като бъде зададена. Константа в езика за програмиране Go се декларира с помощта на ключовата дума „const“
Създайте файл, наречен constant.go и със следния код
package main import ("fmt") func main() { const b =10 fmt.Println(b) b = 30 fmt.Println(b) }
Изпълнете go run constant.go за да видите резултата като
.constant.go:7:4: cannot assign to b
Примери за цикъл
Циклите се използват за многократно изпълнение на блок от изрази въз основа на условие. Повечето езици за програмиране предоставят 3 вида цикли – for, while, do while. Но езикът за програмиране Go поддържа само for цикъл.
Синтаксисът на Golang for цикъл е
for initialisation_expression; evaluation_expression; iteration_expression{ // one or more statement }
Инициализиране_изразът се изпълнява първо (и само веднъж) в Golang for цикъл.
Тогава evaluation_expression се оценява и ако е вярно, кодът вътре в блока се изпълнява.
Идентификаторът на iteration_expression се изпълнява и evaluation_expression се оценява отново. Ако е вярно, блокът с инструкции се изпълнява отново. Това ще продължи, докато evaluation_expression стане невярно.
Копирайте програмата по-долу във файл и я изпълнете, за да видите Голанг за отпечатване на числа от 1 до 5
package main import "fmt" func main() { var i int for i = 1; i <= 5; i++ { fmt.Println(i) } }
Изходът е
1 2 3 4 5
Ако иначе
If else е условен оператор. Синаксът е
if condition{ // statements_1 }else{ // statements_2 }
Тук условието се оценява и ако е вярно ще бъдат изпълнени statement_1, else statements_2 ще бъдат изпълнени.
Можете да използвате оператора if и без else. Можете също да имате верижни изрази if else. Програмите по-долу ще обяснят повече за if else.
Изпълнете програмата по-долу. Той проверява дали дадено число x е по-малко от 10. Ако е така, ще отпечата „x е по-малко от 10“
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x < 10 fmt.Println("x is less than 10") } }
Тук, тъй като стойността на x е по-голяма от 10, изразът вътре в блоковото условие if няма да бъде изпълнен.
Сега вижте програмата по-долу. В този урок за език за програмиране Go имаме блок else, който ще се изпълни при неуспешна оценка на if.
package main import "fmt" func main() { var x = 50 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else { //Executes if x >= 10 fmt.Println("x is greater than or equals 10") } }
Тази програма ще ви даде резултат
x is greater than or equals 10
Сега в този урок за Go ще видим програма с множество if else блокове (верижни if else). Изпълнете примера за Go по-долу. Той проверява дали дадено число е по-малко от 10 или е между 10-90 или по-голямо от 90.
package main import "fmt" func main() { var x = 100 if x < 10 { //Executes if x is less than 10 fmt.Println("x is less than 10") } else if x >= 10 && x <= 90 { //Executes if x >= 10 and x<=90 fmt.Println("x is between 10 and 90") } else { //Executes if both above cases fail i.e x>90 fmt.Println("x is greater than 90") } }
Тук първо условието if проверява дали x е по-малко от 10 и не е. Така че проверява следващото условие (иначе ако) дали е между 10 и 90, което също е невярно. Така че след това изпълнява блока под секцията else, която дава изхода
x is greater than 90
прекъсвач
Switch е друг условен оператор. Операторите Switch оценяват израз и резултатът се сравнява с набор от налични стойности (случаи). След като бъде намерено съвпадение, операторите, свързани с това съвпадение (случай), се изпълняват. Ако не бъде намерено съвпадение, нищо няма да бъде изпълнено. Можете също така да добавите случай по подразбиране за превключване, който ще се изпълни, ако не бъдат намерени други съвпадения. Синтаксисът на превключвателя е
switch expression { case value_1: statements_1 case value_2: statements_2 case value_n: statements_n default: statements_default }
Тук стойността на израза се сравнява със стойностите във всеки случай. След като бъде намерено съвпадение, операторите, свързани с този случай, се изпълняват. Ако не бъде намерено съвпадение, се изпълняват операторите под раздела по подразбиране.
Изпълнете програмата по-долу
package main import "fmt" func main() { a,b := 2,1 switch a+b { case 1: fmt.Println("Sum is 1") case 2: fmt.Println("Sum is 2") case 3: fmt.Println("Sum is 3") default: fmt.Println("Printing default") } }
Ще получите изхода като
Sum is 3
Променете стойността на a и b на 3 и резултатът ще бъде
Printing default
Можете също така да имате няколко стойности в регистър, като ги разделите със запетая.
Масивите
Масивът представлява фиксиран размер, наречена последователност от елементи от един и същи тип. Не можете да имате масив, който съдържа едновременно цели числа и знаци в него. Не можете да промените размера на масив, след като определите размера.
Синтаксисът за деклариране на масив е
var arrayname [size] type
На всеки елемент от масива може да бъде присвоена стойност с помощта на синтаксиса
arrayname [index] = value
Индексът на масива започва от 0 до размер-1.
Можете да присвоите стойности на елементите на масива по време на декларацията, като използвате синтаксиса
arrayname := [size] type {value_0,value_1,…,value_size-1}
Можете също така да игнорирате параметъра size, докато декларирате масива със стойности, като замените size с ... и компилаторът ще намери дължината от броя на стойностите. Синтаксисът е
arrayname := […] type {value_0,value_1,…,value_size-1}
Можете да намерите дължината на масива, като използвате синтаксиса
len(arrayname)
Изпълнете примера за Go по-долу, за да разберете масива
package main import "fmt" func main() { var numbers [3] string //Declaring a string array of size 3 and adding elements numbers[0] = "One" numbers[1] = "Two" numbers[2] = "Three" fmt.Println(numbers[1]) //prints Two fmt.Println(len(numbers)) //prints 3 fmt.Println(numbers) // prints [One Two Three] directions := [...] int {1,2,3,4,5} // creating an integer array and the size of the array is defined by the number of elements fmt.Println(directions) //prints [1 2 3 4 5] fmt.Println(len(directions)) //prints 5 //Executing the below commented statement prints invalid array index 5 (out of bounds for 5-element array) //fmt.Println(directions[5]) }
Продукция
Two 3 [One Two Three] [1 2 3 4 5] 5
Golang Slice и функция за добавяне
Срезът е част или сегмент от масив. Или това е изглед или частичен изглед на основен масив, към който сочи. Можете да осъществите достъп до елементите на срез, като използвате името на среза и индексния номер точно както правите в масив. Не можете да промените дължината на масив, но можете да промените размера на парче.
Съдържанието на среза всъщност са указателите към елементите на масива. Това означава ако промените някой елемент в секция, съдържанието на основния масив също ще бъде засегнато.
Синтаксисът за създаване на срез е
var slice_name [] type = array_name[start:end]
Това ще създаде отрязък с име на slice_name от масив с име на array_name с елементи от началото на индекса до end-1.
Сега в този урок за Golang ще изпълним програмата по-долу. Програмата ще създаде срез от масива и ще го отпечата. Освен това можете да видите, че модифицирането на съдържанието в среза ще промени действителния масив.
package main import "fmt" func main() { // declaring array a := [5] string {"one", "two", "three", "four", "five"} fmt.Println("Array after creation:",a) var b [] string = a[1:4] //created a slice named b fmt.Println("Slice after creation:",b) b[0]="changed" // changed the slice data fmt.Println("Slice after modifying:",b) fmt.Println("Array after slice modification:",a) }
Това ще отпечата резултата като
Array after creation: [one two three four five] Slice after creation: [two three four] Slice after modifying: [changed three four] Array after slice modification: [one changed three four five]
Има определени функции като Golang len, Golang append, които можете да приложите върху срезове
len(slice_name) – връща дължината на среза
добавяне (име_на_срез, стойност_1, стойност_2) – Golang append се използва за добавяне на value_1 и value_2 към съществуващ срез.
добави (slice_nale1,slice_name2…) – добавя slice_name2 към slice_name1
Изпълнете следната програма.
package main import "fmt" func main() { a := [5] string {"1","2","3","4","5"} slice_a := a[1:3] b := [5] string {"one","two","three","four","five"} slice_b := b[1:3] fmt.Println("Slice_a:", slice_a) fmt.Println("Slice_b:", slice_b) fmt.Println("Length of slice_a:", len(slice_a)) fmt.Println("Length of slice_b:", len(slice_b)) slice_a = append(slice_a,slice_b...) // appending slice fmt.Println("New Slice_a after appending slice_b :", slice_a) slice_a = append(slice_a,"text1") // appending value fmt.Println("New Slice_a after appending text1 :", slice_a) }
Резултатът ще бъде
Slice_a: [2 3] Slice_b: [two three] Length of slice_a: 2 Length of slice_b: 2 New Slice_a after appending slice_b : [2 3 two three] New Slice_a after appending text1 : [2 3 two three text1]
Програмата първо създава 2 среза и отпечатва дължината им. След това той добави един срез към друг и след това добави низ към получения срез.
Функции
Функцията представлява блок от изрази, който изпълнява конкретна задача. Декларацията на функция ни казва името на функцията, типа на връщането и входните параметри. Дефиницията на функцията представлява кода, съдържащ се във функцията. Синтаксисът за деклариране на функцията е
func function_name(parameter_1 type, parameter_n type) return_type { //statements }
Параметрите и типовете връщане не са задължителни. Освен това можете да върнете множество стойности от функция.
Сега в този урок за Golang, нека стартираме следния пример за Golang. Тук функцията с име calc приема 2 числа и извършва събиране и изваждане и връща и двете стойности.
package main import "fmt" //calc is the function name which accepts two integers num1 and num2 //(int, int) says that the function returns two values, both of integer type. func calc(num1 int, num2 int)(int, int) { sum := num1 + num2 diff := num1 - num2 return sum, diff } func main() { x,y := 15,10 //calls the function calc with x and y an d gets sum, diff as output sum, diff := calc(x,y) fmt.Println("Sum",sum) fmt.Println("Diff",diff) }
Резултатът ще бъде
Sum 25 Diff 5
Услуги
Пакетите се използват за организиране на кода. В голям проект не е възможно да се напише код в един файл. Езикът за програмиране Go ни позволява да организираме кода в различни пакети. Това увеличава четливостта и повторното използване на кода. Една изпълнима програма Go трябва да съдържа пакет с име main и изпълнението на програмата започва от функцията с име main. Можете да импортирате други пакети в нашата програма, като използвате синтаксиса
import package_name
Ще видим и обсъдим в този урок за Golang как да създаваме и използваме пакети в следния пример за Golang.
Стъпка 1) Създайте файл с име package_example.go и добавете кода по-долу
package main import "fmt" //the package to be created import "calculation" func main() { x,y := 15,10 //the package will have function Do_add() sum := calculation.Do_add(x,y) fmt.Println("Sum",sum) }
В горната програма fmt е пакет, който езикът за програмиране Go ни предоставя главно за I/O цели. Освен това можете да видите пакет с име изчисление. Вътре в main() можете да видите сума на стъпка := изчисление.Do_add(x,y). Това означава, че извиквате функцията Do_add от изчислението на пакета.
Стъпка 2) Първо, трябва да създадете изчислението на пакета в папка със същото име под папката src на go. Инсталираният път на go може да бъде намерен от променливата PATH.
За mac намерете пътя, като изпълните echo $PATH
Така че пътят е /usr/local/go
За Windows намерете пътя, като изпълните echo %GOROOT%
Тук пътят е C:\Go\
Стъпка 3) Отидете до папката src (/usr/local/go/src за mac и C:\Go\src за windows). Сега от кода името на пакета е изчисление. Go изисква пакетът да бъде поставен в директория със същото име под src директория. Създайте директория с име изчисление в папката src.
Стъпка 4) Създайте файл, наречен calc.go (Можете да дадете произволно име, но името на пакета в кода има значение. Тук трябва да е изчисление) в директорията за изчисление и добавете кода по-долу
package calculation func Do_add(num1 int, num2 int)(int) { sum := num1 + num2 return sum }
Стъпка 5) Изпълнете командата go install от директорията за изчисление, която ще компилира calc.go.
Стъпка 6) Сега се върнете към package_example.go и стартирайте go run package_example.go. Резултатът ще бъде Sum 25.
Имайте предвид, че името на функцията Do_add започва с главна буква. Това е така, защото в Go, ако името на функцията започва с главна буква, това означава, че други програми могат да я видят (достъп), в противен случай другите програми нямат достъп до нея. Ако името на функцията беше do_add, тогава щеше да получиш грешката
не може да препраща към неекспортирано име калкулация.calc..
Отлагане и подреждане на отлагания
Операторите Defer се използват за отлагане на изпълнението на извикване на функция, докато функцията, която съдържа оператора defer, завърши изпълнението.
Нека научим това с пример:
package main import "fmt" func sample() { fmt.Println("Inside the sample()") } func main() { //sample() will be invoked only after executing the statements of main() defer sample() fmt.Println("Inside the main()") }
Резултатът ще бъде
Inside the main() Inside the sample()
Тук изпълнението на sample() се отлага, докато завърши изпълнението на включващата функция(main()).
Подреждането на отлагане използва множество изрази за отлагане. Да предположим, че имате множество оператори за отлагане във функция. Go поставя всички отложени извиквания на функции в стек и след като обхващащата функция се върне, подредените функции се изпълняват в Последно влязло, първо излязло (LIFO) ред. Можете да видите това в примера по-долу.
Изпълнете кода по-долу
package main import "fmt" func display(a int) { fmt.Println(a) } func main() { defer display(1) defer display(2) defer display(3) fmt.Println(4) }
Резултатът ще бъде
4 3 2 1
Тук кодът в main() се изпълнява първо, а след това отложените извиквания на функции се изпълняват в обратен ред, т.е. 4, 3,2,1.
указатели
Преди да обясним указателите, нека първо обсъдим оператора „&“. Операторът '&' се използва за получаване на адреса на променлива. Това означава, че '&a' ще отпечата адреса на паметта на променлива a.
В този урок по Golang ще изпълним програмата по-долу, за да покажем стойността на променлива и адреса на тази променлива
package main import "fmt" func main() { a := 20 fmt.Println("Address:",&a) fmt.Println("Value:",a) }
Резултатът ще бъде
Address: 0xc000078008 Value: 20
Променливата указател съхранява адреса на паметта на друга променлива. Можете да дефинирате указател, като използвате синтаксиса
var variable_name *type
Звездичката (*) означава, че променливата е указател. Ще разберете повече, като изпълните програмата по-долу
package main import "fmt" func main() { //Create an integer variable a with value 20 a := 20 //Create a pointer variable b and assigned the address of a var b *int = &a //print address of a(&a) and value of a fmt.Println("Address of a:",&a) fmt.Println("Value of a:",a) //print b which contains the memory address of a i.e. &a fmt.Println("Address of pointer b:",b) //*b prints the value in memory address which b contains i.e. the value of a fmt.Println("Value of pointer b",*b) //increment the value of variable a using the variable b *b = *b+1 //prints the new value using a and *b fmt.Println("Value of pointer b",*b) fmt.Println("Value of a:",a)}
Резултатът ще бъде
Address of a: 0x416020 Value of a: 20 Address of pointer b: 0x416020 Value of pointer b 20 Value of pointer b 21 Value of a: 21
Структури
Структурата е дефиниран от потребителя тип данни, който сам по себе си съдържа още един елемент от същия или различен тип.
Използването на структура е процес от 2 стъпки.
Първо създайте (декларирайте) структурен тип
Второ, създайте променливи от този тип, за да съхранявате стойности.
Структурите се използват главно, когато искате да съхранявате свързани данни заедно.
Помислете за информация за служител, която съдържа име, възраст и адрес. Можете да се справите с това по 2 начина
Създайте 3 масива – един масив съхранява имената на служителите, един съхранява възрастта и третият съхранява възрастта.
Декларирайте структурен тип с 3 полета - име, адрес и възраст. Създайте масив от този структурен тип, където всеки елемент е структурен обект с име, адрес и възраст.
Първият подход не е ефективен. В този вид сценарии структурите са по-удобни.
Синтаксисът за деклариране на структура е
type structname struct { variable_1 variable_1_type variable_2 variable_2_type variable_n variable_n_type }
Пример за декларация на структура е
type emp struct { name string address string age int }
Тук се създава нов тип, дефиниран от потребителя, наречен emp. Сега можете да създавате променливи от типа emp, като използвате синтаксиса
var variable_name struct_name
Пример за това е
var empdata1 emp
Можете да зададете стойности за empdata1 като
empdata1.name = "John" empdata1.address = "Street-1, Bangalore" empdata1.age = 30
Можете също да създадете структурна променлива и да присвоите стойности от
empdata2 := emp{"Raj", "Building-1, Delhi", 25}
Тук трябва да поддържате реда на елементите. Raj ще бъде съпоставен с името, следващият елемент с адреса и последният с възрастта.
Изпълнете кода по-долу
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //function which accepts variable of emp type and prints name property func display(e emp) { fmt.Println(e.name) } func main() { // declares a variable, empdata1, of the type emp var empdata1 emp //assign values to members of empdata1 empdata1.name = "John" empdata1.address = "Street-1, London" empdata1.age = 30 //declares and assign values to variable empdata2 of type emp empdata2 := emp{"Raj", "Building-1, Paris", 25} //prints the member name of empdata1 and empdata2 using display function display(empdata1) display(empdata2) }
Продукция
John Raj
Методи (не функции)
Методът е функция с аргумент приемник. Archiструктурно, това е между ключовата дума func и името на метода. Синтаксисът на метода е
func (variable variabletype) methodName(parameter1 paramether1type) { }
Нека преобразуваме горната примерна програма да използва методи вместо функция.
package main import "fmt" //declared the structure named emp type emp struct { name string address string age int } //Declaring a function with receiver of the type emp func(e emp) display() { fmt.Println(e.name) } func main() { //declaring a variable of type emp var empdata1 emp //Assign values to members empdata1.name = "John" empdata1.address = "Street-1, Lodon" empdata1.age = 30 //declaring a variable of type emp and assign values to members empdata2 := emp { "Raj", "Building-1, Paris", 25} //Invoking the method using the receiver of the type emp // syntax is variable.methodname() empdata1.display() empdata2.display() }
Go не е обектно-ориентиран език и няма понятието клас. Методите дават усещане за това, което правите в обектно-ориентирани програми, където функциите на клас се извикват с помощта на синтаксиса objectname.functionname()
Concurrency
Go поддържа едновременно изпълнение на задачи. Това означава, че Go може да изпълнява няколко задачи едновременно. Тя е различна от концепцията за паралелизъм. При паралелизма една задача се разделя на малки подзадачи и се изпълняват паралелно. Но при паралелност множество задачи се изпълняват едновременно. Паралелността се постига в Go с помощта на Goroutines и Channels.
Goroutines
Goroutine е функция, която може да работи едновременно с други функции. Обикновено, когато се извика функция, управлението се прехвърля в извиканата функция и след завършване на изпълнението управлението се връща към извикващата функция. След това извикващата функция продължава своето изпълнение. Извикващата функция изчаква извиканата функция да завърши изпълнението, преди да продължи с останалите изрази.
Но в случай на goroutine, извикващата функция няма да чака изпълнението на извиканата функция да завърши. Той ще продължи да се изпълнява със следващите оператори. Можете да имате множество goroutines в една програма.
Също така, основната програма ще излезе, след като завърши изпълнението на операторите си и няма да изчака завършването на извиканите goroutines.
Goroutine се извиква с ключова дума go, последвана от извикване на функция.
Пример
go add(x,y)
Ще разберете goroutines с примерите на Golang по-долу. Изпълнете програмата по-долу
package main import "fmt" func display() { for i:=0; i<5; i++ { fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() //The main() continues without waiting for display() for i:=0; i<5; i++ { fmt.Println("In main") } }
Резултатът ще бъде
In main In main In main In main In main
Тук основната програма завърши изпълнението дори преди горутината да започне. Display() е goroutine, която се извиква с помощта на синтаксиса
go function_name(parameter list)
В горния код main() не изчаква display() да завърши и main() завърши изпълнението си, преди display() да изпълни своя код. Така операторът за печат в display() не се отпечатва.
Сега модифицираме програмата така, че да отпечатва и изразите от display(). Добавяме времезакъснение от 2 секунди в for цикъла на main() и 1 секунда забавяне в for цикъла на display().
package main import "fmt" import "time" func display() { for i:=0; i<5; i++ { time.Sleep(1 * time.Second) fmt.Println("In display") } } func main() { //invoking the goroutine display() go display() for i:=0; i<5; i++ { time.Sleep(2 * time.Second) fmt.Println("In main") } }
Резултатът ще бъде донякъде подобен на
In display In main In display In display In main In display In display In main In main In main
Тук можете да видите, че двата цикъла се изпълняват по припокриващ се начин поради едновременното изпълнение.
Канали
Каналите са начин функциите да комуникират помежду си. Може да се мисли като среда за мястото, където една рутина поставя данни и е достъпна от друга рутина в сървъра на Golang.
Каналът може да бъде деклариран със синтаксиса
channel_variable := make(chan datatype)
Пример:
ch := make(chan int)
Можете да изпращате данни към канал, като използвате синтаксиса
channel_variable <- variable_name
Пример
ch <- x
Можете да получавате данни от канал, като използвате синтаксиса
variable_name := <- channel_variable
Пример
y := <- ch
В горните примери за goroutine на езика Go сте видели, че основната програма не чака goroutine. Но това не е така, когато са включени канали. Да предположим, че ако goroutine избутва данни към канал, main() ще изчака оператора, получаващ данни от канала, докато получи данните.
Ще видите това в примерите за език Go по-долу. Първо напишете нормална goroutine и вижте поведението. След това модифицирайте програмата да използва канали и вижте поведението.
Изпълнете програмата по-долу
package main import "fmt" import "time" func display() { time.Sleep(5 * time.Second) fmt.Println("Inside display()") } func main() { go display() fmt.Println("Inside main()") }
Резултатът ще бъде
Inside main()
Основният() завърши изпълнението и излезе преди изпълнението на goroutine. Така че печатът вътре в display() не се изпълнява.
Сега променете горната програма, за да използвате канали и вижте поведението.
package main import "fmt" import "time" func display(ch chan int) { time.Sleep(5 * time.Second) fmt.Println("Inside display()") ch <- 1234 } func main() { ch := make(chan int) go display(ch) x := <-ch fmt.Println("Inside main()") fmt.Println("Printing x in main() after taking from channel:",x) }
Резултатът ще бъде
Inside display() Inside main() Printing x in main() after taking from channel: 1234
Тук се случва main() при достигане на x := <-ch ще чака данни за канал ch. Display() изчаква 5 секунди и след това изпраща данни към канала ch. Main() при получаване на данните от канала се деблокира и продължава изпълнението си.
Подателят, който изпраща данни към канала, може да информира получателите, че няма да се добавят повече данни към канала, като затвори канала. Това се използва главно, когато използвате цикъл за изпращане на данни към канал. Каналът може да бъде затворен с помощта на
close(channel_name)
И в края на приемника е възможно да се провери дали каналът е затворен с помощта на допълнителна променлива, докато се извличат данни от канала с помощта
variable_name, status := <- channel_variable
Ако състоянието е True, това означава, че сте получили данни от канала. Ако е невярно, това означава, че се опитвате да четете от затворен канал
Можете също да използвате канали за комуникация между goroutines. Трябва да използвате 2 goroutines – едната изпраща данни към канала, а другата получава данните от канала. Вижте програмата по-долу
package main import "fmt" import "time" //This subroutine pushes numbers 0 to 9 to the channel and closes the channel func add_to_channel(ch chan int) { fmt.Println("Send data") for i:=0; i<10; i++ { ch <- i //pushing data to channel } close(ch) //closing the channel } //This subroutine fetches data from the channel and prints it. func fetch_from_channel(ch chan int) { fmt.Println("Read data") for { //fetch data from channel x, flag := <- ch //flag is true if data is received from the channel //flag is false when the channel is closed if flag == true { fmt.Println(x) }else{ fmt.Println("Empty channel") break } } } func main() { //creating a channel variable to transport integer values ch := make(chan int) //invoking the subroutines to add and fetch from the channel //These routines execute simultaneously go add_to_channel(ch) go fetch_from_channel(ch) //delay is to prevent the exiting of main() before goroutines finish time.Sleep(5 * time.Second) fmt.Println("Inside main()") }
Тук има 2 подпрограми, едната изпраща данни към канала, а другата отпечатва данни към канала. Функцията add_to_channel събира числата от 0 до 9 и затваря канала. Едновременно с това функцията fetch_from_channel чака при
x, флаг := <- ch и след като данните станат налични, ги отпечатва. Той излиза, след като флагът е false, което означава, че каналът е затворен.
Изчакването в main() се дава, за да се предотврати излизането от main(), докато goroutines завършат изпълнението.
Изпълнете кода и вижте изхода като
Read data Send data 0 1 2 3 4 5 6 7 8 9 Empty channel Inside main()
Изберете
Select може да се разглежда като команда за превключване, която работи на канали. Тук операторите за case ще бъдат операция на канал. Обикновено всеки оператор на случай ще бъде опит за четене от канала. Когато някой от случаите е готов (каналът е прочетен), тогава операторът, свързан с този случай, се изпълнява. Ако са готови няколко случая, той ще избере случаен. Можете да имате случай по подразбиране, който се изпълнява, ако нито един от случаите не е готов.
Нека видим кода по-долу
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements wait for data in the chan1 or chan2. //chan2 gets data first since the delay is only 2 sec in data2(). //So the second case will execute and exits the select block select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) } }
Изпълнението на горната програма ще даде резултата:
from data2()
Тук командата select изчаква данните да бъдат налични във всеки от каналите. Data2() добавя данни към канала след сън от 2 секунди, което ще доведе до изпълнение на втория случай.
Добавете случай по подразбиране към select в същата програма и вижте резултата. Тук, при достигане на избрания блок, ако нито един случай няма готови данни за канала, той ще изпълни блока по подразбиране, без да чака данните да бъдат налични на който и да е канал.
package main import "fmt" import "time" //push data to channel with a 4 second delay func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } //push data to channel with a 2 second delay func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { //creating channel variables for transporting string values chan1 := make(chan string) chan2 := make(chan string) //invoking the subroutines with channel variables go data1(chan1) go data2(chan2) //Both case statements check for data in chan1 or chan2. //But data is not available (both routines have a delay of 2 and 4 sec) //So the default block will be executed without waiting for data in channels. select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) default: fmt.Println("Default case executed") } }
Тази програма ще даде резултат:
Default case executed
Това е така, защото когато избраният блок е достигнат, нито един канал няма данни за четене. И така, случайът по подразбиране се изпълнява.
Мютекс
Mutex е кратката форма за взаимно изключване. Mutex се използва, когато не искате да разрешите достъп до ресурс от множество подпрограми едновременно. Mutex има 2 метода – заключване и отключване. Mutex се съдържа в пакета за синхронизиране. Така че трябва да импортирате пакета за синхронизиране. Изявленията, които трябва да бъдат взаимно изключващи се изпълнени, могат да бъдат поставени в mutex.Lock() и mutex.Unlock().
Нека научим mutex с пример, който брои колко пъти се изпълнява цикъл. В тази програма очакваме рутината да изпълни цикъл 10 пъти и броят се съхранява в сума. Извиквате тази рутина 3 пъти, така че общият брой трябва да бъде 30. Броят се съхранява в глобална променлива count.
Първо, стартирате програмата без mutex
package main import "fmt" import "time" import "strconv" import "math/rand" //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
Вижте резултата
Count after i=1 Count: 11 Count after i=3 Count: 12 Count after i=2 Count: 13 Final Count: 13
Резултатът може да е различен, когато го изпълните, но крайният резултат няма да бъде 30.
Тук това, което се случва, е, че 3 програми се опитват да увеличат броя на циклите, съхранени в променливата count. Да предположим, че в даден момент броят е 5 и goroutine1 ще увеличи броя до 6. Основните стъпки включват
Копиране на броя на темп
Увеличете темп
Запазете температурата обратно, за да преброите
Да предположим, че скоро след изпълнение на стъпка 3 от goroutine1; друга goroutine може да има стара стойност, да речем, че 3 изпълнява горните стъпки и съхранява 4 обратно, което е грешно. Това може да бъде предотвратено чрез използване на mutex, което кара други процедури да чакат, когато една програма вече използва променливата.
Сега ще стартирате програмата с mutex. Тук гореспоменатите 3 стъпки се изпълняват в мютекс.
package main import "fmt" import "time" import "sync" import "strconv" import "math/rand" //declare a mutex instance var mu sync.Mutex //declare count variable, which is accessed by all the routine instances var count = 0 //copies count to temp, do some processing(increment) and store back to count //random delay is added between reading and writing of count variable func process(n int) { //loop incrementing the count by 10 for i := 0; i < 10; i++ { time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) //lock starts here mu.Lock() temp := count temp++ time.Sleep(time.Duration(rand.Int31n(2)) * time.Second) count = temp //lock ends here mu.Unlock() } fmt.Println("Count after i="+strconv.Itoa(n)+" Count:", strconv.Itoa(count)) } func main() { //loop calling the process() 3 times for i := 1; i < 4; i++ { go process(i) } //delay to wait for the routines to complete time.Sleep(25 * time.Second) fmt.Println("Final Count:", count) }
Сега изходът ще бъде
Count after i=3 Count: 21 Count after i=2 Count: 28 Count after i=1 Count: 30 Final Count: 30
Тук получаваме очаквания резултат като краен резултат. Тъй като операторите за четене, увеличаване и обратно записване на броя се изпълняват в мютекс.
Обработка на грешки
Грешките са необичайни състояния като затваряне на файл, който не е отворен, отваряне на файл, който не съществува и т.н. Функциите обикновено връщат грешки като последна върната стойност.
Примерът по-долу обяснява повече за грешката.
package main import "fmt" import "os" //function accepts a filename and tries to open it. func fileopen(name string) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { fmt.Println(er) return }else{ fmt.Println("file opened", f.Name()) } } func main() { fileopen("invalid.txt") }
Изходът ще бъде:
open /invalid.txt: no such file or directory
Тук се опитахме да отворим несъществуващ файл и той върна грешката на променливата er. Ако файлът е валиден, тогава грешката ще бъде нула
Персонализирани грешки
Използвайки тази функция, можете да създавате персонализирани грешки. Това се прави с помощта на New() на пакета за грешка. Ще пренапишем горната програма, за да използваме персонализираните грешки.
Стартирайте програмата по-долу
package main import "fmt" import "os" import "errors" //function accepts a filename and tries to open it. func fileopen(name string) (string, error) { f, er := os.Open(name) //er will be nil if the file exists else it returns an error object if er != nil { //created a new error object and returns it return "", errors.New("Custom error message: File name is wrong") }else{ return f.Name(),nil } } func main() { //receives custom error or nil after trying to open the file filename, error := fileopen("invalid.txt") if error != nil { fmt.Println(error) }else{ fmt.Println("file opened", filename) } }
Изходът ще бъде:
Custom error message:File name is wrong
Тук area() връща площта на квадрат. Ако въведеното е по-малко от 1, тогава area() връща съобщение за грешка.
Четене на файлове
Файловете се използват за съхраняване на данни. Go ни позволява да четем данни от файловете
Първо създайте файл, data.txt, в настоящата директория със съдържанието по-долу.
Line one Line two Line three
Сега стартирайте програмата по-долу, за да видите, че отпечатва съдържанието на целия файл като изход
package main import "fmt" import "io/ioutil" func main() { data, err := ioutil.ReadFile("data.txt") if err != nil { fmt.Println("File reading error", err) return } fmt.Println("Contents of file:", string(data)) }
Тук данните, err := ioutil.ReadFile(“data.txt”) четат данните и връщат последователност от байтове. По време на печат се преобразува във формат на низ.
Писане на файлове
Ще видите това с програма
package main import "fmt" import "os" func main() { f, err := os.Create("file1.txt") if err != nil { fmt.Println(err) return } l, err := f.WriteString("Write Line one") if err != nil { fmt.Println(err) f.Close() return } fmt.Println(l, "bytes written") err = f.Close() if err != nil { fmt.Println(err) return } }
Тук се създава файл test.txt. Ако файлът вече съществува, съдържанието на файла се съкращава. Writeline() се използва за запис на съдържанието във файла. След това затворихте файла с Close().
Cheat Sheet
В този урок за Go разгледахме,
тема | Descriptйон | Синтаксис |
---|---|---|
Основни видове | Числови, низови, логически | |
Променливи | Деклариране и присвояване на стойности на променливи | тип променлива име_на_променлива променлива име_на_променлива тип = стойност променлива име_на_променлива1, име_на_променлива2 = стойност1, стойност2 име_на_променлива := стойност |
константи | Променливи, чиято стойност не може да бъде променена след присвояване | const променлива = стойност |
За Loop | Изпълнение на оператори в цикъл. | за инициализиращ_израз; израз_за_оценка; итерационен_израз{ // едно или повече твърдения } |
Ако иначе | Това е условно твърдение | ако условие{ // изявления_1 Останало {} // изявления_2 } |
превключите | Условен оператор с множество случаи | израз за превключване { case value_1: изявления_1 case value_2: изявления_2 case value_n: изявления_n по подразбиране: statements_default } |
Array | Наименована последователност от елементи от един и същи тип с фиксиран размер | име на масив := [размер] тип {стойност_0,стойност_1,…,размер_на_стойност-1} |
Парче | Част или сегмент от масив | var slice_name [] тип = array_name[начало:край] |
Функции | Блок от оператори, който изпълнява конкретна задача | func име_на_функция(тип_параметър_1, тип_параметър_n) return_type { //изявления } |
Услуги | Използват се за организиране на кода. Увеличава четливостта и повторното използване на кода | импортиране име_на_пакета |
Отложи | Отлага изпълнението на функция, докато съдържащата функция завърши изпълнението | defer function_name(parameter_list) |
указатели | Съхранява адреса на паметта на друга променлива. | променлива име_на_променлива *тип |
структура | Дефиниран от потребителя тип данни, който сам по себе си съдържа още един елемент от същия или различен тип | тип structname struct { променлива_1 променлива_1_тип променлива_2 променлива_2_тип променлива_n променлива_n_тип } |
Методи | Методът е функция с аргумент приемник | func (променлива variabletype) methodName(parameter_list) { } |
Горутин | Функция, която може да работи едновременно с други функции. | отидете име_на_функция(списък_параметри) |
Канал | Начин функциите да комуникират помежду си. Носител, към който една рутина поставя данни и е достъпен от друга рутина. | Декларирам: ch := make(chan int) Изпращане на данни към канала: канал_променлива <- име_на_променлива Получаване от канал: име_на_променлива := <- променлива_на_канал |
Изберете | Изявление за превключване, което работи на канали. Изявленията за случая ще бъдат операция на канал. Когато някой от каналите е готов с данни, тогава операторът, свързан с този случай, се изпълнява | изберете { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) } |
Мютекс | Mutex се използва, когато не искате да разрешите достъп до ресурс от множество подпрограми едновременно. Mutex има 2 метода – заключване и отключване | mutex.Lock() //изявления mutex.Unlock(). |
Четене на файлове | Чете данните и връща последователност от байтове. | Данни, грешка := ioutil.ReadFile(име на файл) |
Напиши файл | Записва данни във файл | l, err := f.WriteString(text_to_write) |