Слайс - это структура данных в основе которой лежит массив.
Слайс имеет три поля: указатель на массив, длину, вместимость. Длина - это число элементов, на которое ссылается слайс. Вместимость - это число элементов лежащего в основе массива (начиная с элемента, на который ссылается указатель слайс).
Можно делать много слайсов одного массива. При этом слайсинг не производит копирование данных исходного массива если хватает вместимости. Срезу, в отличии от массива, можно динамически изменять вместимость с помощью функции append, при этом массив может быть скопирован в новый массив большего размера, если не хватает вместимости исходного массива.
В функции слайс передается по значению, при этом если в функции делается например append переданному слайсу и происходит перевыделение массива в следствии не хватки вместимости, то исходный слайс не меняется в нем остается указатель на изначальный массив.
По-умолчанию при заполнении слайс увеличивает свою вместимость x2, если вместимость слайса превышает 1024, то увелечение происходит частями, например на 1/4
Слайс имеет три поля: указатель на массив, длину, вместимость. Длина - это число элементов, на которое ссылается слайс. Вместимость - это число элементов лежащего в основе массива (начиная с элемента, на который ссылается указатель слайс).
Можно делать много слайсов одного массива. При этом слайсинг не производит копирование данных исходного массива если хватает вместимости. Срезу, в отличии от массива, можно динамически изменять вместимость с помощью функции append, при этом массив может быть скопирован в новый массив большего размера, если не хватает вместимости исходного массива.
В функции слайс передается по значению, при этом если в функции делается например append переданному слайсу и происходит перевыделение массива в следствии не хватки вместимости, то исходный слайс не меняется в нем остается указатель на изначальный массив.
По-умолчанию при заполнении слайс увеличивает свою вместимость x2, если вместимость слайса превышает 1024, то увелечение происходит частями, например на 1/4
map, она же хэшмап в go - это тип данных, представляющий собой таблицу ключей ссылок на
значения.
Map позволяет обращаться к значениям по ключам за время O(1). Под капотом мапа работает с помощью функции, позволяющей вычислить по ключу адрес ссылки на значение.
Под капотом у map - массив массивов, который при заполнении выделяется в новой памяти уже большего размера, старая память при этом возвращается. При этом в случае удаления элементов из map delete (map, key), память не возвращается.
Map позволяет обращаться к значениям по ключам за время O(1). Под капотом мапа работает с помощью функции, позволяющей вычислить по ключу адрес ссылки на значение.
Под капотом у map - массив массивов, который при заполнении выделяется в новой памяти уже большего размера, старая память при этом возвращается. При этом в случае удаления элементов из map delete (map, key), память не возвращается.
Строки в go иммутабельные, при любом изменении создается новая строка, занимающая новую память,
значение которой копируется из старой с нужными изменениями, поэтому для создания строк стоит
использовать strings.Builder.
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer
for i := 0; i < 1000; i++ {
buffer.WriteString("a")
}
fmt.Println(buffer.String())
// Или
var sb strings.Builder
for i := 0; i < 1000; i++ {
sb.WriteString("a")
}
fmt.Println(sb.String())
}
defer функции помещаются в отдельный стэк вызовов, когда родительская функция переходит
в состояние выхода например при return, defer функции достаются из стэка в обратном порядке то
есть первый вошел последний вышел и вызываются.
После вызова всех defer функций, родительская функция завершает работу.
Аргументы defer функции вычисляются в момент когда она попадает в стэк defer функций.
defer может модифицировать возвращаемый функцией именованный результат
После вызова всех defer функций, родительская функция завершает работу.
Аргументы defer функции вычисляются в момент когда она попадает в стэк defer функций.
defer может модифицировать возвращаемый функцией именованный результат
package main
import "fmt"
func Triple(n int) (r int) {
defer func() {
r += n // modify the return value
}()
return n + n // <=> r = n + n; return
}
func main() {
fmt.Println(Triple(5)) // 15
}
Горутина — это функция, которая может работать параллельно с другими функциями.
В основе горутин асинхронные вызовы, грин-треды и кооперативная (до 1.14, после 1.14 планировщик решает когда переключить горутины - вытесняющая) многозадачность.
В зависимости от настройки GOMAXPROCS горутины могут выполняться как в одном физическом потоке, так и в нескольких.
В основе горутин асинхронные вызовы, грин-треды и кооперативная (до 1.14, после 1.14 планировщик решает когда переключить горутины - вытесняющая) многозадачность.
В зависимости от настройки GOMAXPROCS горутины могут выполняться как в одном физическом потоке, так и в нескольких.
Канал — это объект связи, с помощью которого горутины обмениваются данными.
Технически это конвейер (или труба), откуда можно считывать или помещать данные. То есть одна горутина может отправить данные в канал, а другая — считать помещенные в этот канал данные.
Канал может передавать данные только одного типа, данные других типов через это канал передавать невозможно.
Нулевое значение канала — это
Когда вы помещаете данные в канал, горутина блокируется до тех пор, пока данные не будут считаны другой горутиной из этого канала. deadlock (когда данные из канала ни кто не читает) приведет к аварийному завершению программы
Чтение из закрытого канала мгновенно прекратится и выдастся пустой результат без какой либо ошибки.
При закрытие уже закрытого канала произойдёт паника
Каналы бывают буферизированные и не буферизированные. Когда размер буфера больше 0, горутина не блокируется до тех пор, пока буфер не будет заполнен. Когда буфер заполнен, любые значения отправляемые через канал, добавляются к буферу, отбрасывая предыдущее значение, которое доступно для чтения (где горутина будет заблокирована). Но есть один подвох, операция чтения на буферизированном канале является жадной, таким образом, как только операция чтения началась, она не будет завершена до полного опустошения буфера. Это означает, что горутина будет считывать буфер канала без блокировки до тех пор, пока буфер не станет пустым.
Фактически что бы разблокировать горутину ждущую буферизированный канал, нужно помести n+1 значение в канал (буфер 3, помещаем 4 значения).
Подобно срезам, буферизированный канал имеет длину и емкость. Длина канала — это количество значений в очереди (не считанных) в буфере канала, емкость — это размер самого буфера канала.
Однонаправленные каналы - это, канал, который сможет только считывать данные или канал который сможет только записывать их. Использование однонаправленного канала улучшает безопасность типов в программe.
Технически это конвейер (или труба), откуда можно считывать или помещать данные. То есть одна горутина может отправить данные в канал, а другая — считать помещенные в этот канал данные.
Канал может передавать данные только одного типа, данные других типов через это канал передавать невозможно.
Нулевое значение канала — это
nil
.
nil канал бесполезен, что бы канал создать нужно выполнить make(chan int)
Когда вы помещаете данные в канал, горутина блокируется до тех пор, пока данные не будут считаны другой горутиной из этого канала. deadlock (когда данные из канала ни кто не читает) приведет к аварийному завершению программы
Чтение из закрытого канала мгновенно прекратится и выдастся пустой результат без какой либо ошибки.
При закрытие уже закрытого канала произойдёт паника
Каналы бывают буферизированные и не буферизированные. Когда размер буфера больше 0, горутина не блокируется до тех пор, пока буфер не будет заполнен. Когда буфер заполнен, любые значения отправляемые через канал, добавляются к буферу, отбрасывая предыдущее значение, которое доступно для чтения (где горутина будет заблокирована). Но есть один подвох, операция чтения на буферизированном канале является жадной, таким образом, как только операция чтения началась, она не будет завершена до полного опустошения буфера. Это означает, что горутина будет считывать буфер канала без блокировки до тех пор, пока буфер не станет пустым.
Фактически что бы разблокировать горутину ждущую буферизированный канал, нужно помести n+1 значение в канал (буфер 3, помещаем 4 значения).
Подобно срезам, буферизированный канал имеет длину и емкость. Длина канала — это количество значений в очереди (не считанных) в буфере канала, емкость — это размер самого буфера канала.
Однонаправленные каналы - это, канал, который сможет только считывать данные или канал который сможет только записывать их. Использование однонаправленного канала улучшает безопасность типов в программe.
// def := make(chan int) // двунаправленный канал
// roc := make(<-chan int) // канал для чтения
// soc := make(chan<- int) // канал для записи
package main
import "fmt"
func main() {
fmt.Println("main() started")
c := make(chan string)
go func(roc <-chan string) {
fmt.Println("Hello " + <-roc + "!")
}(c)
c <- "John"
fmt.Println("main() stopped")
}
Канал каналов (пример)
select похож на switch без аргументов, но он может использоваться только для операций с
каналами. Оператор select используется для выполнения операции только с одним из множества
каналов, условно выбранного блоком case.
Так же как и switch, оператор select поддерживает оператор default. default делает блок select всегда неблокируемым.
Можно использовать, чтобы избежать deadlock.
Так же может работать с nil каналом.
Так же как и switch, оператор select поддерживает оператор default. default делает блок select всегда неблокируемым.
Можно использовать, чтобы избежать deadlock.
Так же может работать с nil каналом.
package main
import "fmt"
func service(c chan string) {
c <- "response"
}
func main() {
var chan1 chan string
go service(chan1)
select {
case res := <-chan1:
fmt.Println("Response from chan1", res)
default:
fmt.Println("No response")
}
}
// Return: No response
Канал каналов (пример)
sync.WaitGroup это счётчик для синзронизации горутин.
В wg.Add() задаём кол-во горутин, в горутинах вызываем wg.Done(),
в main ждём пока выполнятся все горутины с помощью wg.Wait().
package main
import (
"fmt"
"sync"
)
func service(wg *sync.WaitGroup, instance int) {
fmt.Println("Service called on instance", instance)
wg.Done()
}
func main() {
fmt.Println("main() started")
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go service(&wg, i)
}
wg.Wait()
fmt.Println("main() stopped")
}
Mutex один из простых способов организовать конкурентный доступ к данным. Mutex защищает память
от гонки данных.
Существуют обычные mutex и RWMutex, RW нужен тогда когда код предназначен больше для конкурентного чтения и меньше для записи. В таком случае RW покажет большую производительность.
Атомик не блокирующий. Он использует cas инстуркции. Атомик проще и быстрее мьютекса. Но мьютекс не ждет получения блокировки и сразу возвращает статус о неудачной попытке захвата блокировки. Атомик будет крутится в цикле аля спин лок в ожидании блокировки.
Существуют обычные mutex и RWMutex, RW нужен тогда когда код предназначен больше для конкурентного чтения и меньше для записи. В таком случае RW покажет большую производительность.
package main
import (
"fmt"
"sync"
)
var i int
func worker(wg *sync.WaitGroup, m *sync.Mutex) {
m.Lock()
i = i + 1
m.Unlock()
wg.Done()
}
func main() {
var wg sync.WaitGroup
var m sync.Mutex
for i := 0; i < 1000; i++ {
wg.Add(1)
go worker(&wg, &m)
}
wg.Wait()
fmt.Println("value of i after 1000 operations is", i)
}
Atomic так же может помочь справиться с гонкой данных и может показать большую
производительность,
кроме случаев когда необходимо сохранить большую map, вам придется копировать ее каждый раз при
обновлении map, что делает ее неэффективной.Атомик не блокирующий. Он использует cas инстуркции. Атомик проще и быстрее мьютекса. Но мьютекс не ждет получения блокировки и сразу возвращает статус о неудачной попытке захвата блокировки. Атомик будет крутится в цикле аля спин лок в ожидании блокировки.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var ops uint64
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
for c := 0; c < 1000; c++ {
atomic.AddUint64(&ops, 1)
}
wg.Done()
}()
}
wg.Wait()
fmt.Println("ops:", ops)
}
Паника это способ выйти из функции аварийно.
Паникам переполнения стэка вызовов и выхода за пределы памяти нельзя сделать recover.
Физически состояние паники это поле в структуре горутины.
Физически состояние паники это поле в структуре горутины.
Интерфейсный тип в Go — это своего рода определение. Он определяет и описывает конкретные
методы, которые должны быть у какого-то другого типа.
В отличие от других языков, в go структура будет имплементировать интерфейс автоматически если в структуре реализованы все методы этого интерфейса.
Пустой интерфейс - это не определённый (любой) тип. Может использоваться для реализации полиморфизма, т.е. возможности передавать в метод динамические типы и обрабатывать их внутри метода
В отличие от других языков, в go структура будет имплементировать интерфейс автоматически если в структуре реализованы все методы этого интерфейса.
Пустой интерфейс - это не определённый (любой) тип. Может использоваться для реализации полиморфизма, т.е. возможности передавать в метод динамические типы и обрабатывать их внутри метода
func Scan(val interface{}) error {
switch v := val.(type) {
case []byte:
return json.Unmarshal(v, &d)
case string:
return json.Unmarshal([]byte(v), &d)
default:
return errors.New(fmt.Sprintf("Unsupported type: %T", v))
}
}
Дженерики - встроенная (с версии 1.18) конструкция, позволяющая создавать динамически
типизированные аргументы.
// Встроенные интерфейсы comparable и any
type Number interface {
int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
type Vector[T Number] []T
func AddVector[T Number](vec1 Vector[T], vec2 Vector[T]) Vector[T] {
var result Vector[T]
for i := range vec1 {
result = append(result, vec1[i]+vec2[i])
}
return result
}
func main() {
v1 := Vector[int]{1, 2, 3}
v2 := Vector[int]{3, 4, 5}
result := AddVector(v1, v2)
fmt.Println(result)
}
// Ещё пример
func existsInSlice[T comparable](val T, values []T) bool {
for _, v := range values {
if val == v {
return true
}
}
return false
}