Go Level Up
268 subscribers
3 photos
5 links
Go Hard or Go Home

Контакт для связи со мной: @sQUARyss
Download Telegram
Что выведет код?

func main() {
mainSl := []string{"Test", "slices", "copying"}

var sl []string
copy(sl, mainSl)

sl1 := make([]string, 1, 3)
copy(sl1, mainSl)

fmt.Println(sl, sl1)
}


#код
➡️Код выведет: [] [Test]⬅️

Лучшее объяснение этого вывода состоит в идее copy. Предназначена эта функция для копирования элементов слайса-родителя в слайс-ребенок, отвязывая от ссылки на массив родитель
🧐(частая ошибка начинающих), однако делает она это достаточно топорно, она не может ни проинициализировать слайс-ребенок, ни изменить его как-либо, чтобы все элементы родителя скопировались.📌

🖱Всё, что умеет эта функция - глупое перекладывание элементов по ячейкам.

То есть, если есть выделенная под элемент ячейка слайса, то copy сделает свою работу, а вот если ячейки не оказалось, то copy просто пожимает плечами.
➡️

Теперь важный момент на понимание слайса: выделенные ячейки - это не cap! Это len! Ровно по этому числу мы понимаем сколько проинициализированных ячеек в слайсе

💬Теперь к результатам:

В sl мы ничего не получили по причине того, что инициализация вида
var sl []string

это инициализация nil-слайса, у которого нет ни длины, ни капасити
🔜copy не смог ничего перетащить

💡Во втором примере у sl1 уже установлена длина 1 и капасити 3, следовательно copy переносит в инициализированную ячейку элемент

Польза copy:
На просторах коммерческой разработки мало встречал использования этой функции, хотя и не понимаю почему
😢 Встречал много решений, где люди копировали слайс через форики, чтобы потом его мутировать/менять и не завязываться двумя слайсами на один массив родитель (опасно!). На мой взгляд, решение громоздкое и некрасивое. copy гораздо эффективнее и лаконичнее, и к тому же оно может также использоваться как получение так называемого ломтика.⬅️

func main() {
sl := []int{1, 2, 3, 4}

part := sl[:2]

partByCopy := make([]int, 2)
copy(partByCopy, sl)

// Изменим sl, чтобы показать наследование ссылки на массив-родитель
sl[0] = 5

fmt.Println(part, partByCopy)
// Получаем вывод:[5 2] [1 2] -> получение базового ломтика, приводит к риску мутаций, в отличие от copy
}
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍22
Давайте согласуем удобное время для мита по написанию микросервиса
Anonymous Poll
19%
Сегодня
15%
25 июня
19%
26 июня
11%
27 июня
67%
Выходные
Что произойдет при запуске программы?

var m = map[string]int{"a": 1}

func main() {
go Read()
time.Sleep(1 * time.Second)
go Write()
time.Sleep(1 * time.Minute)
}

func Read() {
for {
fmt.Println(m["a"])
}
}

func Write() {
for i := 0; ; i++ {
m[fmt.Sprintf("%d", i)] = i
}
}


#код
Голосуем за время воскресного мита
Anonymous Poll
11%
11:00
17%
12:00
44%
17:00
28%
21:00
17%
Не могу в вс🙁
Go Level Up
Что произойдет при запуске программы? var m = map[string]int{"a": 1} func main() { go Read() time.Sleep(1 * time.Second) go Write() time.Sleep(1 * time.Minute) } func Read() { for { fmt.Println(m["a"]) } } func Write() { for i := 0;…
📌При запуске программы произойдет - бесконечный вывод 1 и фатальная ошибка fatal error: concurrent map read and map write

➡️Важно! Данный результат произойдет при версии го, где мапа реализована в виде бакетов, то есть до swiss table. Какой результат будет в новой версии, требует отдельного поста.➡️

💻Итак, вернемся к нашей старой доброй мапе на бакетах. Каждый раз, когда показываю менти этот код, они вчитываются в код и пытаются найти здесь скрытый смысл. Но нет, по сути все, что достаточно увидеть - чтение и запись в мапу из горутин 🔜🔜🔜 ошибка. Дело в том, что мапа не потокобезопасна. 🥳

➡️На моках постоянно отвечают так😳Но не понимают почему так и что это значит. Это значит, что как раз-таки из мапы нельзя читать и писать одновременно в связи с вероятностью обратиться к несуществующему эвакуированному куску памяти или вовсе вызвать гонку данных.

💬Причина: при эвакуации старые бакеты постепенно освобождаются и ключи-значения переходят в новые выделенные бакеты бОльшего количества. Так вот когда мы пишем мы сталкиваемся с риском эвакуации, а когда читаем с риском обратиться к несуществующему удаленному блоку памяти или непредсказуемым данным(из-за data race)🖥
👍321
➡️Итак, фаталы и конкурентные ошибки мапы это классика начинающего Go-разраба💻

🖱Но что делать, когда так хочется заюзать мапу конкурентно, чтобы не блокироваться на вставку/чтение, и при этом не упасть с злосчастным fatal error. 😨

💻Пример решения строится на мьютексах, что логично, нам ведь по факту просто нужно блокировать блоки памяти, чтобы они не сбежали от нас чисто случайно в другие недра ноликов и единичек.🖱

📌В данном примере лучше юзать не просто мьютекс, а RWMutex. Разница? Read-Write Mutex позволяет блокировать блок памяти только на запись, и при этом разрешать этот же блок читать.👣

type SafeMap struct {
sync.RWMutex
data map[string]int
}

func main() {
protectedMap := SafeMap{
data: map[string]int{"a": 1},
}

go protectedMap.Read()
time.Sleep(1 * time.Second)
go protectedMap.Write()
time.Sleep(1 * time.Minute)
}

func (sm *SafeMap) Read() {
for {
sm.RLock()
fmt.Println(sm.data["a"])
sm.RUnlock()
}
}

func (sm *SafeMap) Write() {
for i := 0; ; i++ {
sm.Lock()
sm.data[fmt.Sprintf("%d", i)] = i
sm.Unlock()
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
83👍3
Коллеги, спасибо за участие в опросе! Большинство выбрало вариант 17:00. 💻

🖱Сегодня в 17:00 (МСК) созвон — обсудим как писать микросервис на го. Жду всех!📌
Please open Telegram to view this post
VIEW IN TELEGRAM
31👍1
Коллеги, созвон по написанию микросервиса начинается. Всех жду

https://us04web.zoom.us/j/79268156565?pwd=dSYcbdVxZC0K0IJx97ubNHgoYIGt8w.1
4
Коллеги извиняюсь, зум у меня странно работает, крашится. Переходим в гугл мит https://meet.google.com/bfc-mioz-qhz
1
💻Благодарю всех, кто присоединился к созвону! На мой взгляд получилось удачно) Надеюсь всем было полезно🤍

💬На этой неделе я доделаю конспект с подробным разбором всего, что мы успели обсудить: поэтапно опишу создание микросервиса, приведу примеры кода, объясню почему и зачем использую конкретный паттерн разработки, ⚡️добавлю ответы на ваши вопросы которые были в созвоне

📌Оставайтесь на связи — скоро будет ещё больше интересного! 💎🔥
Please open Telegram to view this post
VIEW IN TELEGRAM
14👍4
Что выведет код?

func main() {
runtime.GOMAXPROCS(1)

wg := sync.WaitGroup{}

for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()

fmt.Println(n)
}(i)
}

wg.Wait()
}
📌Код выведет: 9 0 1 2 3 4 5 6 7 8

🔥Почему? Обратим внимание на GOMAXPROCS(1), данная строчка говорит о том, что планировщик может использовать только один процессор(планировщика!!!) для выполнения задач. 💬

💻Теперь нужно вспомнить про LRQ(Local Run Queue), которая закреплена за каждым процессором, в нашем случае она единственная. Структура очереди не простая, а с оптимизацией: последняя горутина, может выполнится первой, без очереди, придя в одноэлементный стэк LIFO. 🤍💬

Что? Зачем?! Это сделано потому, что, есть ненулевая вероятность, того что на машине(ядре) контекст выполнения соответствует тому, что нужен именно этой горутине, поэтому часто имеет смысл выполнить её вперёд остальных. Также эту оптимизацию объясняют так, что мы оптимизируем время ожидания горутин, чтобы последняя пришедшая ждала не так долго, потому что возможно она предназначена для более приоритетных задач, чем те кто в обычной очереди


🟢Итог: имеем одну LRQ, которая имеет вид [LIFO][FIFO], где LIFO одна ячейка, а FIFO хранит все остальное в обычном порядке. Собственно поэтому придя последней, 9-ка вывелась первой, подтвердив одноэлементный стэк, а все остальные числа вывелись из FIFO👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3211
Привет всем!➡️

💻Не успеваю закончить конспект по разработке микросервиса – выложу его в начале следующей недели.👈

Мне показалось, что конспект стоит модифицировать, чтобы вам было проще, в связи с этим хотел бы узнать ваше мнение какой формат вам удобнее и понятнее.💬

⭕️Буду рад обратной связи!😳
Please open Telegram to view this post
VIEW IN TELEGRAM
4
Что выведет данный код?

func main() {
items := []string{
"Кошка",
"Собака",
"Роза",
}

animals := items[0:2]
plants := items[2:3]

animals = append(animals, "Хомяк")

fmt.Printf("Животные: %v (len: %d, cap: %d)\n", animals, len(animals), cap(animals))
fmt.Printf("Растения: %v (len: %d, cap: %d)\n", plants, len(plants), cap(plants))
}
Код выведет: Животные: [Кошка Собака Хомяк] (len: 3, cap: 3) Растения: [Хомяк] (len: 1, cap: 1)🤩

💬Давайте последовательно, как получились такие капасити и длина?
Тут важно вспомнить теорию ломтиков - когда мы отрезаем ломтик, его капасити высчитывается как капасити родительского слайса вычесть стартовый индекс ломтика. 📌Таким образом конечные cap(animals) = 3 - 0 = 3; cap(plants) = 3 - 2 = 1. С длинной все проще, сколько элементов по факту в ломтике, столько и длина➡️

🟢Почему хомяк закрался в растения?
💻Animals при достижении append хранит в себе [Кошка, Собака], при этом len=2, так как у нас два элемента, а cap = 3 (считаем как в предыдущем пункте). ВАЖНОу ломтиков такой же массив родитель что у items, получается если был items из трех элементов мы отрезали два, то по факту мы просто на том же блоке памяти добавили ссылок для новых ломтиков. То есть мы смотрим в animals и видим [Кошка, Собака], но в памяти все равно набор данных строк лежит как [Кошка, Собака, Роза], просто у ломтика конечный индекс другой. ➡️Поэтому когда мы добавляем в animals "Хомяк", капасити нам позволяет в тот же массив родитель добавить, так как длинна 2 < cap 3. ➡️Поэтому в тот же массив родитель мы и производим наши изменения, а так как индекс ломтика завершился на Собака, то когда происходит append мы инкрементим индекс и просто перезаписываем Роза на Хомяк в памяти🔘
41