Горячее
Лучшее
Свежее
Подписки
Сообщества
Блоги
Эксперты
Войти
Забыли пароль?
или продолжите с
Создать аккаунт
Регистрируясь, я даю согласие на обработку данных и условия почтовых рассылок.
или
Восстановление пароля
Восстановление пароля
Получить код в Telegram
Войти с Яндекс ID Войти через VK ID
ПромокодыРаботаКурсыРекламаИгрыПополнение Steam
Пикабу Игры +1000 бесплатных онлайн игр Возглавьте армию своей страны в войне с коварным врагом. Управляйте ресурсами, принимайте ключевые решения и ведите Граднар через суровый конфликт. Ваши действия определяют будущее, приводя страну к победе или поражению.

Симулятор войны: 1985

Мидкорные, Стратегии, Симуляторы

Играть

Топ прошлой недели

  • solenakrivetka solenakrivetka 7 постов
  • Animalrescueed Animalrescueed 53 поста
  • ia.panorama ia.panorama 12 постов
Посмотреть весь топ

Лучшие посты недели

Рассылка Пикабу: отправляем самые рейтинговые материалы за 7 дней 🔥

Нажимая «Подписаться», я даю согласие на обработку данных и условия почтовых рассылок.

Спасибо, что подписались!
Пожалуйста, проверьте почту 😊

Помощь Кодекс Пикабу Команда Пикабу Моб. приложение
Правила соцсети О рекомендациях О компании
Промокоды Биг Гик Промокоды Lamoda Промокоды МВидео Промокоды Яндекс Маркет Промокоды Пятерочка Промокоды Aroma Butik Промокоды Яндекс Путешествия Промокоды Яндекс Еда Постила Футбол сегодня
0 просмотренных постов скрыто
DELETED

Учению Go верить мы будем⁠⁠

1 год назад

Великое сознание, растворяющееся в коде,

Пусть наш путь будет как строгая типизация,

Очищаемый от ошибок как garbage collector.

Всемогущий Go, помоги нам найти утечки памяти в душе,

Чтобы мы достигли высокой производительности в жизни.

Не прилипая к статическим методам,

Стремимся к асинхронной гармонии.

Каждый goroutine - шаг к просветлению,

Каждая defer - путь к мудрости.

Пусть пакеты нашего духа будут чисты,

Пусть зависимости будут минимальны.

Как структура данных, пусть сердце наше будет открыто

Для бесконечного потока знаний и любви.

Восхваляем конкуренцию, но с синхронизацией,

Ищем истины в рефлексии и интерфейсах.

Будем поддерживать легковесность своего бытия,

Как компилируется Go - быстро и надежно.

О, GoLang, даруй нам прозрение

В циклах жизни и переменных судьбы.

Позволь нам стать божественными программистами

В великом проекте бытия.

(с) мое

Показать полностью
[моё] Медитация Совершенство Программирование Golang Текст
3
Ado27nb

Быть или не быть?⁠⁠

1 год назад

Всём доброго времени суток!
Это мой первый пост ( о помощи)
Работаю монтажником оконный конструкций. Зп хорошая, не жалуюсь. Но так сказать всё надоело, прикипели(работаю с 8 утра и примерно до 6 вечера, всегда по разному, ещё есть маленький и любимый сын)и понимаю, что всю жизнь не смогу так работать. Тянет меня в IT, начал многи посты читать, какие вообще направления есть. Понравился язык Golang (самообучаюсь), но не знаю правильно ли я что делаю и то ли направление выбрал и что я вообще хочу от изучения данного языка.
Помощь прошу в том чтобы подсказать или посоветовать мб какие либо онлайн курсы
или литературу дополнительно(читаю сейчас Донована)
Извиняюсь за корявость поста
Всем добра
p.s. Вообще для меня сначала в идеале, это ТГ боты с AI. Но с тем временем, которое у меня есть для самообучения уйдёт лет 10, а то и больше

#работа
#IT
#обучение
#junior
#програмирование

Показать полностью
[моё] IT Golang Искусственный интеллект Обучение Работа
28
FreshAngry007

Go и Горутины⁠⁠

1 год назад

Горутины в языке программирования Go — это легковесные потоки, используемые для выполнения функций параллельно с другими горутинами в одном и том же адресном пространстве процесса. Горутины являются одной из ключевых особенностей Go и предоставляют очень эффективный способ для обработки параллельных задач и асинхронного выполнения.

Легковесность

Горутины очень легковесны по сравнению с традиционными потоками. Создание горутины требует всего несколько килобайт стека, который может увеличиваться и уменьшаться по мере необходимости. Это позволяет создавать тысячи и даже миллионы горутин в одном приложении без значительного потребления ресурсов.

Мультиплексирование на меньшем количестве ОС потоков

Горутины мультиплексируются на меньшем количестве потоков операционной системы. Это значит, что даже при блокировке одной горутины (например, при ожидании ввода/вывода), другие горутины продолжат выполняться на других потоках ОС, что обеспечивает высокую производительность и эффективность использования ресурсов.

Планировщик

Go имеет свой встроенный планировщик, который распределяет горутины по доступным потокам операционной системы. Планировщик использует механизм M:N, где M горутин мультиплексируются на N потоков операционной системы. Планировщик Go работает на уровне пользовательского пространства и оптимизирован для работы с большим количеством горутин.

Синхронизация и коммуникация

Для синхронизации и обмена данными между горутинами в Go используется концепция каналов. Каналы позволяют безопасно передавать сообщения между горутинами, обеспечивая при этом синхронизацию доступа к данным.

Каналы в Go — это средства для синхронизации и коммуникации между горутинами, позволяя им безопасно обмениваться данными. Работа с каналами в Go обеспечивает синхронизацию доступа к данным без необходимости использования блокировок или других механизмов синхронизации, что делает код более простым и безопасным.

Блокировка

  • Небуферизованные каналы блокируют отправляющую горутину до тех пор, пока другая горутина не прочитает из канала, и наоборот — получающая горутина блокируется до тех пор, пока значение не будет отправлено.

  • Буферизованные каналы позволяют отправлять значения в канал без блокировки до тех пор, пока буфер не будет заполнен. Аналогично, данные могут быть прочитаны из канала, если он не пуст.

Закрытие канала

Канал можно закрыть с помощью функции close, чтобы указать, что больше нет значений для отправки. После закрытия канала нельзя отправлять в него данные, но можно продолжать получать данные, которые были в нем до закрытия:

close(ch)

Попытка отправить данные в закрытый канал вызовет панику.

Проверка, закрыт ли канал

При чтении из канала можно использовать вторую переменную, чтобы проверить, закрыт ли канал:

value, ok := <-ch // ok будет false, если канал закрыт и в нем больше нет значений

Использование каналов для синхронизации

Каналы могут использоваться не только для обмена данными, но и для синхронизации выполнения горутин, например, чтобы дождаться завершения работы нескольких горутин перед продолжением выполнения основной программы.

package main

func main() {

done := make(chan bool)

go func() {

// Выполнение некоторой работы...

done <- true // Сигнализация о завершении работы

}()


<-done // Ожидание сигнала о завершении работы

}

Давайте рассмотрим простой, но весьма показательный пример, который может использоваться в продакшене: параллельную загрузку данных из нескольких источников с помощью горутин и каналов. Этот подход часто используется при работе с внешними API или при выполнении других I/O-операций, требующих асинхронности и конкурентности.

Цель

Мы хотим параллельно запросить данные из трех разных источников. Для упрощения примера представим, что эти "запросы" просто спят (time.Sleep) разное количество времени для имитации задержки сети. После "запроса" каждая горутина отправляет результат в канал. Основная горутина ожидает все результаты и затем продолжает выполнение.

package main

import (

"fmt"

"math/rand"

"time"

)

func fetchData(source string, ch chan<- string) {


time.Sleep(time.Duration(rand.Intn(3)) * time.Second)

ch <- source + " data"

}

func main() {

ch := make(chan string, 3)

go fetchData("Source 1", ch)

go fetchData("Source 2", ch)

go fetchData("Source 3", ch)

for i := 0; i < 3; i++ {

result := <-ch

fmt.Println(result)

}

fmt.Println("All data fetched")

}

Подводные камни

Утечки горутин

Горутины, которые никогда не завершаются, могут привести к утечкам памяти. Это часто происходит, когда горутина ожидает на канале, который никогда не будет закрыт, или ожидает на блокировке, которая никогда не освободится.

Как избежать: Убедитесь, что все горутины имеют чёткие условия завершения и что все каналы, на которых они ожидают, будут в какой-то момент закрыты.

Проблемы с синхронизацией и гонки данных

Доступ к общему состоянию из нескольких горутин без надлежащей синхронизации может привести к гонкам данных, что делает поведение программы непредсказуемым.

Как избежать: Используйте мьютексы (sync.Mutex) или каналы для синхронизации доступа к общим ресурсам.

Мёртвая блокировка (Deadlock)

Мёртвая блокировка может произойти, когда две или более горутин ожидают друг друга, образуя цикл ожидания, из которого невозможно выйти.

package main

func main() {

ch1 := make(chan int)

ch2 := make(chan int)

go func() {

<-ch1

ch2 <- 1

}()

go func() {

<-ch2

ch1 <- 1

}()

// Мёртвая блокировка: ни один из каналов не получит значение.

<-ch1 // fatal error: all goroutines are asleep - deadlock!

}

Как избежать

go vet — это инструмент командной строки в Go, предназначенный для анализа исходного кода на предмет общих ошибок, таких как гонки данных, неправильное использование синтаксиса, несоответствия типов и многое другое. Он не заменяет тесты, но может помочь выявить потенциальные проблемы в коде на ранних этапах разработки.

Race Detector - Запуск приложения с включенным детектором гонок (-race флаг компилятора) может помочь выявить некоторые виды блокировок и условий гонки, хотя его основная цель — обнаружение гонок данных.

package main

import (

"fmt"

"sync"

)

func main() {

var counter int

var wg sync.WaitGroup

for i := 0; i < 1000; i++ {

wg.Add(1)

go func() {

counter++

wg.Done()

}()

}

wg.Wait()

fmt.Println("Final counter value:", counter)

}

// go run -race main.go

/*

WARNING: DATA RACE

Read at 0x00c0000140b8 by goroutine 7:

main.main.func1()

main.go:14 +0x33

Final counter value: 820

Found 2 data race(s)

exit status 66

*/

правильный вариант

правильный вариант

package main

import (

"fmt"

"sync"

)

func main() {

var counter int

var wg sync.WaitGroup

var mutex sync.Mutex // Добавляем мьютекс для синхронизации

for i := 0; i < 1000; i++ {

wg.Add(1)

go func() {

mutex.Lock()  // Захватываем мьютекс перед изменением счётчика

counter++

mutex.Unlock() // Освобождаем мьютекс после изменения счётчика

wg.Done()

}()

}

wg.Wait()

fmt.Println("Final counter value:", counter)

}

Дебаггеры и инструменты профилирования: Использование инструментов, таких как pprof или отладчиков, которые могут помочь вам визуализировать и анализировать блокировки и другие проблемы синхронизации в вашем коде.

Создадим файл main.go с простым кодом, который намеренно создаёт нагрузку на CPU и память, чтобы мы могли увидеть что-то интересное в отчётах pprof.

Этот код запускает бесконечный цикл, который выполняет функцию computeFunction, создающую нагрузку. Кроме того, он запускает HTTP-сервер с поддержкой pprof на порту 6060. Запустите программу

go run main.go

Откройте другой терминал и используйте go tool pprof для сбора различных видов профилей (CPU, память, блокировки и т. д.) с вашей работающей программы.

Профиль использования CPU

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

Эта команда соберёт информацию о производительности CPU за последние 30 секунд.

Профиль использования памяти

go tool pprof http://localhost:6060/debug/pprof/heap

Эта команда соберёт информацию о выделении памяти программой.

После сбора профиля pprof откроет интерактивную консоль, в которой вы можете выполнять различные команды для анализа собранных данных, например:

  • top показывает топ функций по использованию ресурсов.

  • list [function name] показывает детализацию использования ресурсов для указанной функции.

  • web генерирует граф вызовов в виде SVG и открывает его в вашем браузере (требует Graphviz).

Установить Graphviz можно так

sudo apt-get update && sudo apt-get install graphviz

Эти команды помогут вам выявить узкие места в производительности и оптимизировать ваш код.

Когда вы используете pprof с Graphviz для визуализации графов на Debian (или любой другой Unix-подобной системе), местоположение сохранения графа зависит от того, как вы вызываете команду визуализации. Если вы используете команду web в интерактивном режиме pprof, она обычно открывает SVG-файл непосредственно в вашем браузере, не сохраняя его локально. Однако, если вы хотите сохранить граф в файл, вы можете использовать другие команды в pprof, такие как svg или png, для создания файла определенного формата. В интерактивном режиме pprof введите команду для сохранения в SVG:

(pprof) svg > cpu-usage.svg

Эта команда создаст файл cpu-usage.svg в текущем рабочем каталоге. Если вы хотите сохранить файл в другом месте, укажите полный путь. Аналогично, если вы предпочитаете другие форматы, такие как PNG, используйте команду png.

Внимательный анализ логики и дизайна: Часто лучший способ избежать мёртвых блокировок — это тщательно продумать дизайн вашей конкурентной логики, чтобы исключить взаимные блокировки на этапе планирования.

Показать полностью 12
[моё] Golang Программирование IT Длиннопост
3
FreshAngry007

Go и Generics⁠⁠

1 год назад

Дженерики (обобщенное программирование) были введены в язык программирования Go в версии 1.18. Они позволяют писать функции и типы данных, которые могут работать с различными типами данных без потери типобезопасности. До появления дженериков разработчики Go часто использовали интерфейсы и тип interface{} для создания функций и структур, способных работать с любыми типами данных, что могло привести к ошибкам времени выполнения из-за неправильного преобразования типов.

Синтаксис

Дженерики в Go определяются с использованием квадратных скобок [] после имени функции или типа. Внутри скобок указываются один или несколько типовых параметров, которые затем можно использовать в теле функции или типа как обычные типы данных.

В этом примере T является типовым параметром, который может быть заменен любым типом данных. Ключевое слово any означает, что T может быть любым типом.

Преимущества

  • Повторное использование кода: Дженерики позволяют создавать гибкие функции и типы данных, которые можно использовать с различными типами данных без дублирования кода.

  • Типобезопасность: В отличие от использования interface{}, дженерики обеспечивают проверку типов на этапе компиляции, что уменьшает риск ошибок времени выполнения.

  • Производительность: Использование дженериков может улучшить производительность, так как избавляет от необходимости преобразования типов и позволяет компилятору оптимизировать генерируемый код.

Ограничения

  • Синтаксис дженериков может быть сложным для понимания, особенно для начинающих разработчиков.

  • Обобщенное программирование может усложнить структуру кода и его читаемость.

  • Не все существующие библиотеки и инструменты уже полностью поддерживают дженерики.

Несмотря на ограничения, введение дженериков в Go значительно улучшило возможности языка для создания более гибкого и безопасного кода.

Следующий код демонстрирует применение обобщенного программирования в Go с использованием типового параметра T, ограниченного интерфейсом Signed. Интерфейс Signed определяет типы, которые могут быть одним из подписанных (signed) целочисленных типов (int, int8, int16, int32, int64). Структура NumericBox использует этот типовый параметр, что позволяет создавать экземпляры NumericBox для любого подписанного целочисленного типа.

package main

import (

"fmt"

)

type Signed interface {

~int | ~int8 | ~int16 | ~int32 | ~int64

}

// NumericBox - обобщенная структура, ограниченная для работы только с числовыми типами.

type NumericBox[T Signed] struct {

Value T

}

// NewNumericBox создает новый экземпляр NumericBox с заданным числовым значением.

func NewNumericBox[T Signed](value T) NumericBox[T] {

return NumericBox[T]{Value: value}

}

// GetValue возвращает числовое значение из NumericBox.

func (b NumericBox[T]) GetValue() T {

return b.Value

}

func main() {

int_32 := NewNumericBox(int32(123))

fmt.Println("IntBox:", int_32.GetValue()) // Вывод: IntBox: 123

int_64 := NewNumericBox(int64(-123))

fmt.Println("IntBox:", int_64.GetValue()) // Вывод: IntBox: -123

// Следующая строка вызовет ошибку компиляции, так как строка не является числовым типом.

// stringBox := NewNumericBox("Hello, Generics!") // string does not satisfy Signed (string missing in ~int | ~int8 | ~int16 | ~int32 | ~int64)

}

Давайте теперь напишем замер скорости работы, простой структуры, интерфейса и Ginerics

package main

import (

"fmt"

"time"

)

type Animal interface { Get() int }

type Dog struct{ Value int }

func (d Dog) Get() int { return d.Value }

type Signed interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 }

type NumericBox[T Signed] struct { Value T }

// Функция, принимающая конкретный тип.

func printInt(i int) {

fmt.Println(i)

}

// функция, принимающая интерфейс Animal.

func printInterface(animal Animal) {

fmt.Println(animal.Get())

}

// Функция, принимающая экземпляр NumericBox с любым поддерживаемым числовым типом.

func printGinerics[T Signed](box NumericBox[T]) {

fmt.Println(box.Value)

}

func main() {

// Замер производительности при использовании конкретного типа.

startConcrete := time.Now()

for i := 0; i < 1000000; i++ {

printInt(i)

}

resultConcrete := time.Since(startConcrete)

// Замер производительности при использовании интерфейса.

startInter := time.Now()

for i := 0; i < 1000000; i++ {

printInterface(Dog{Value: i})

}

resultInter := time.Since(startInter)

// Замер производительности при использовании дженерика.

startGeneric := time.Now()

for i := 0; i < 1000000; i++ {

printGinerics(NumericBox[int]{Value: i})

}

resultGeneric := time.Since(startGeneric)

fmt.Printf("Using concrete type: %v\n", resultConcrete) // Using concrete type: 869.528353ms

fmt.Printf("Using interface: %v\n", resultInter) // Using interface: 877.926898ms

fmt.Printf("Using generic: %v\n", resultGeneric) // Using generic: 841.916956ms

}

В примере обнаружено, что использование дженериков (обобщенных типов) в Go приводит к самому быстрому времени выполнения по сравнению с использованием конкретных типов и интерфейсов. Это может быть связано с несколькими факторами:

  1. Меньшее количество преобразований типов: Когда вы используете дженерики, Go может генерировать код непосредственно для конкретного типа во время компиляции, что уменьшает необходимость в преобразованиях типов или упаковке/распаковке интерфейсов во время выполнения. Это делает операции более быстрыми.

  2. Прямой доступ к данным: В случае дженериков, доступ к данным происходит напрямую, без дополнительного уровня абстракции, который требуется при использовании интерфейсов. Это уменьшает накладные расходы и может привести к более высокой производительности.

  3. Отсутствие динамического поиска методов: Когда вы используете интерфейсы, Go должен выполнить поиск соответствующего метода в таблице виртуальных методов интерфейса во время выполнения. Дженерики же позволяют избежать этого, так как конкретная реализация метода известна на этапе компиляции.

  4. Компилятор оптимизирует код для дженериков: Компилятор Go может выполнять специализацию кода для дженериков, что означает, что он может генерировать оптимизированный код для каждого конкретного случая использования.

Показать полностью 3
[моё] Программирование IT Golang Длиннопост
5
FreshAngry007

Go и интерфейсы⁠⁠

1 год назад

В Go интерфейсы представляют собой типы, определяющие наборы методов. Они используются для обеспечения полиморфного поведения объектов. В отличие от многих других языков программирования, в Go не требуется явное указание на то, что структура реализует интерфейс. Если структура имеет все методы, описанные в интерфейсе, то считается, что она его реализует.

Основные принципы работы интерфейсов:

  1. Определение интерфейса: Интерфейс в Go описывается как набор методов. Структура или любой другой тип "реализует" интерфейс, если предоставляет реализацию всех методов, перечисленных в интерфейсе.

  2. Полиморфизм: Интерфейсы позволяют переменным иметь различные типы значения во время выполнения программы, что обеспечивает полиморфизм. Это значит, что функция, принимающая интерфейс в качестве аргумента, может работать с любым типом, который реализует этот интерфейс.

  3. Интерфейс{}: Пустой интерфейс interface{} не имеет методов и поэтому может представлять значение любого типа, включая встроенные типы данных. Он часто используется, когда точный тип данных заранее неизвестен.

package main

import "fmt"

// Определение интерфейса Animal

type Animal interface {

Speak() string

}

// Реализация интерфейса Animal для структуры Dog

type Dog struct {}

func (d Dog) Speak() string {

return "Woof!"

}

// Реализация интерфейса Animal для структуры Cat

type Cat struct {}

func (c Cat) Speak() string {

return "Meow"

}

// Функция, принимающая интерфейс Animal

func printAnimalSound(a Animal) {

fmt.Println(a.Speak())

}

func main() {

dog := Dog{}

cat := Cat{}


// Оба типа, Dog и Cat, реализуют интерфейс Animal

// поэтому printAnimalSound может с ними работать

printAnimalSound(dog) // Woof!

printAnimalSound(cat) // Meow

}

В этом примере Dog и Cat являются разными типами, которые реализуют интерфейс Animal, поскольку оба предоставляют метод Speak(). Функция printAnimalSound может принимать любой тип, реализующий Animal.

Подводные камни.

Производительность. Использование интерфейсов несколько снижает производительность по сравнению с работой с конкретными типами из-за необходимости решения типов во время выполнения. Давайте рассмотрим ситуацию, где производительность критична, и использование интерфейсов может влиять на производительность из-за динамического определения типов и вызова методов через интерфейсы. В таких случаях прямое использование конкретных типов может быть более эффективным.

package main


import (

"fmt"

"time"

)


// Animal интерфейс, который реализуют разные животные.

type Animal interface {

Say() string

}


// Dog структура, реализующая интерфейс Animal.

type Dog struct{}


func (d Dog) Say() string {

return "Woof!"

}


// функция, принимающая интерфейс Animal.

func makeNoise(animal Animal) {

fmt.Println(animal.Say())

}


// Функция, принимающая конкретный тип Dog.

func makeDogNoise(dog Dog) {

fmt.Println(dog.Say())

}


func main() {


dog := Dog{}


// Замер производительности при использовании интерфейса.

start_inter := time.Now()

for i := 0; i < 1000000; i++ {

makeNoise(dog)

}

result_iter := time.Since(start_inter)


// Замер производительности при использовании конкретного типа.

start := time.Now()

for i := 0; i < 1000000; i++ {

makeDogNoise(dog)

}

result_concrete := time.Since(start)


fmt.Printf("Using interface: %v\n", result_iter) // Using interface: 841.732596ms

fmt.Printf("Using concrete type: %v\n", result_concrete) // Using concrete type: 822.222233ms


}

В этом примере мы сравниваем время выполнения функций, которые работают с интерфейсом Animal и с конкретным типом Dog. Хотя разница в производительности может быть незначительной для малого количества вызовов, в высокопроизводительных системах или в системах с большим количеством вызовов эта разница может накапливаться.

Преимущества.

  1. Полиморфизм: Интерфейсы позволяют писать функции и методы, которые могут работать с любым типом данных, реализующим интерфейс, не заботясь о конкретной реализации. Это означает, что вы можете легко заменить одну реализацию другой без изменения кода, который использует интерфейс.

  2. Отделение интерфейса от реализации: Интерфейсы позволяют скрыть детали реализации за абстракцией. Это упрощает понимание кода, поскольку вам нужно сосредоточиться только на том, что делает код, а не как он это делает.

  3. Упрощение тестирования: Использование интерфейсов упрощает написание модульных тестов. Вы можете легко создать "моки" или фиктивные реализации интерфейсов для тестирования, не завися от реализаций, которые могут требовать внешних зависимостей (например, базы данных).

  4. Гибкость и расширяемость: Интерфейсы делают ваш код более гибким и легко расширяемым. Вы можете добавлять новые реализации интерфейсов без изменения существующего кода, что особенно полезно в больших и сложных системах.

  5. Взаимозаменяемость компонентов: Поскольку компоненты системы общаются через интерфейсы, вы можете легко заменять одни компоненты другими, которые реализуют те же интерфейсы. Это особенно ценно для создания плагинов, расширений или для поддержки различных вариантов конфигурации системы.

Показать полностью 2
Golang Программирование IT Интерфейс Длиннопост
0
FreshAngry007

Структуры и Go⁠⁠

1 год назад

В Go структуры являются композитным типом данных, позволяющим объединять данные различных типов в одну логическую группу. Структуры часто используются для представления объектов и записей.

Вот основы работы со структурами:

package main

import (

"fmt"

)

// Этот код определяет структуру Person с 3-мя полями: Name (строка), Age (целое число) и hidden (булево значение).

// Приватные поля в Go определяются с помощью нижнего регистра первой буквы имени поля в структуре.

// Они не доступны за пределами пакета, в котором объявлена структура.

type Person struct {

hidden bool

Name string

Age  int

}

// К структурам можно привязывать методы, используя получатель (receiver):

func (p *Person) Greet() string {

return "Hello, " + p.Name

}

// Методы структуры могут иметь доступ к её приватным полям, что позволяет вам управлять доступом к этим полям через публичные методы.

func (e *Person) SetPrivateField(value bool) {

e.hidden = value

}

func (e *Person) GetPrivateField() bool {

return e.hidden

}

// В Go поддерживается встраивание структур что позволяет создавать сложные структуры данных и реализовывать наследование в стиле композиции

// Экземпляр Employee наследует поля и методы Person, к ним можно обращаться так, как если бы они были объявлены напрямую в Employee.

type Employee struct {

Person

Position string

}

func main() {

// Экземпляр структуры может быть создан следующим образом

p := Person{false, "John Doe", 30}


// или с использованием именованных полей для повышения читаемости

p = Person{Name: "John Doe", Age: 30}


// Доступ к полям структуры осуществляется через оператор точки

fmt.Println(p.Name) // Выведет: John Doe


// Вызов метода осуществляется так же, как и доступ к полям

message := p.Greet()

fmt.Println(message) // Выведет: Hello, John Doe


// Go поддерживает анонимные структуры, которые могут быть полезны для одноразовых структур данных:

var user = struct {

ID  int

Name string

}{ID: 1, Name: "John Doe"}

fmt.Println(user) // Выведет: {1 John Doe}

}

В Go методы структур могут иметь два типа приемников (receiver): по значению (копия структуры) и по указателю. Выбор между ними влияет на возможность метода изменять саму структуру и на производительность. Вот основные различия:

Приемник по значению (копия)

Когда метод принимает структуру по значению, он работает с копией этой структуры. Изменения, сделанные внутри метода, не отражаются на оригинальной структуре.

Использование приемника по значению хорошо подходит для маленьких структур или в ситуациях, когда не требуется модифицировать саму структуру в методе.

Приемник по указателю

Когда метод принимает структуру по указателю, он может изменять саму структуру, поскольку работает не с копией, а с реальным адресом структуры в памяти.

Использование приемника по указателю рекомендуется, когда:

  • Необходимо модифицировать саму структуру в методе.

  • Структура достаточно большая, и копирование её значений могло бы отрицательно сказаться на производительности.

Подводные камни

package main

import "fmt"

type Example struct {

Count int

Name string

}

type Derived struct {

Example

Name string

}

type ExampleP struct {

Example *Example

}

func main() {

// Значение по умолчанию для структур

var ex Example

fmt.Println(ex.Count) // Выведет 0, значение по умолчанию для int

fmt.Println(ex.Name)  // Выведет "", значение по умолчанию для string

// Встраивание структур и теневая реализация

d := Derived{}

d.Name = "Derived"

d.Example.Name = "Base"

fmt.Println(d.Name, d.Example.Name) // Чтобы получить доступ к полю Name из встроенной структуры Example, необходимо явно указать путь к нему

// Неинициализированные указатели в структурах

exp := ExampleP{}

fmt.Println(exp.Example.Name) // Приведет к панике, так как ex.Nested == nil

// Сравнение структур

// Не будет работать для структур, содержащих срезы!

ex1 := Example{Count: 5}

ex2 := Example{Count: 5}

fmt.Println(ex1 == ex2) // Выведет "true", так как все поля совпадают

}

В Go, сравнение структур с помощью оператора == будет работать только если все поля структуры поддерживают операцию сравнения. Это означает, что структуры, содержащие следующие типы данных в качестве полей, не могут быть напрямую сравнены:

  1. Срезы (slices): Как вы уже заметили, срезы не поддерживают операцию сравнения. Попытка сравнить структуры, содержащие срезы, приведет к компиляционной ошибке.

  2. Мапы (maps): Также как срезы, мапы не поддерживают операцию сравнения. Структуры с полями типа мапа не могут быть сравнены с помощью ==.

  3. Функции (functions): Функции в Go также не могут быть сравнены напрямую, кроме сравнения на равенство или неравенство с nil.

  4. Каналы (channels): Хотя каналы могут быть сравнены на равенство или неравенство, включая сравнение с nil, сложные сравнения структур, содержащих каналы, могут быть неоднозначными в зависимости от конкретной логики.

  5. Интерфейсы с динамическими типами, не поддерживающими сравнение: Если поле интерфейса содержит значение динамического типа, который сам по себе не поддерживает сравнение, то структура, содержащая такое поле, также не может быть сравнена.

Если вам нужно сравнить структуры, содержащие такие типы данных, вам придется реализовать собственную логику сравнения, которая будет обходиться с каждым полем индивидуально, в зависимости от его типа и предполагаемого значения.

В Go, когда вы передаете структуру в функцию, по умолчанию происходит её копирование. Это значит, что внутри функции создается локальная копия структуры, и любые изменения, сделанные в этой копии, не отразятся на оригинальной структуре. Это поведение соответствует передаче аргументов по значению.

Показать полностью 5
[моё] Golang Программирование Структура IT Длиннопост
21
FreshAngry007

Карты и Go⁠⁠

1 год назад

Карты в Go — это встроенный тип данных, предоставляющий возможность хранения пар ключ-значение. Они похожи на словари или ассоциативные массивы в других языках программирования.

Особенности карт в Go:

  • Динамический размер: Карты могут расти и уменьшаться по мере добавления и удаления элементов, что делает их гибкими для использования в ситуациях, когда количество элементов заранее неизвестно.

  • Типизированные ключи и значения: В картах Go и ключи, и значения могут быть почти любого типа, за исключением тех, которые содержат срезы, карты или другие несравнимые типы. Тип ключа и тип значения для карты указываются при её объявлении.

  • Быстрый доступ: Карты предоставляют быстрый доступ к значениям по ключам. Время доступа к элементу карты не зависит от размера карты, что делает их эффективным выбором для поиска данных.

Работа с картами:

package main

import (

"fmt"

)

func main() {

// Объявление карты

var myMap map[int]string

fmt.Println(myMap) // map[]

// Инициализация карты

myMap = make(map[int]string)

fmt.Println(myMap) // map[]

// Или можно объявить и инициализировать карту одновременно

myMap = make(map[int]string)

fmt.Println(myMap) // map[]

/*

Или можно объявить и инициализировать карту одновременно с емкостью


В отличие от слайсов, у карт нет понятия внешней емкости, доступной для проверки с помощью функции cap().

Это означает, что вызов cap(myMap) будет ошибочным, так как функция cap() не применима к картам.


Начальная емкость карты в Go служит лишь подсказкой для реализации о том, сколько элементов ожидается в карте.

Это может помочь оптимизировать ее внутреннюю структуру и уменьшить количество аллокаций памяти в

начальной фазе использования карты.

*/

myMap = make(map[int]string, 5)

fmt.Println(myMap, len(myMap)) // map[] 0


// Или можно объявить и инициализировать карту одновременно значениями

myMap = map[int]string{1: "a", 2: "b", 3: "c"}

fmt.Println(myMap) // map[1:a 2:b 3:c]

key := 5

// Добавить или изменить значение для ключа "apple"

myMap[key] = "apple"

fmt.Println(myMap) // map[1:a 2:b 3:c 5:apple]

// Получить значение. Если ключ существует, `ok` будет true, иначе false.

if _, ok := myMap[key]; !ok {

panic(fmt.Sprintf("myMap: key %d not found", key)) // panic: myMap: key 6 not found, если ключа 6 не окажется в карте

}

// Удалить элемент с ключом "apple"

// Ну и ни чего не происходит если такого ключа нет

delete(myMap, 6)

fmt.Println(myMap) // map[1:a 2:b 3:c 5:apple]

// Итерация по карте

for k, v := range myMap {

fmt.Println(k, v)

}

}

Подводные камни

1. Изменение карты во время итерации

Модификация карты во время итерации по ней может привести к непредсказуемому поведению. Например, вы можете пропустить некоторые элементы или столкнуться с паникой в рантайме.

Это допустимо в Go и не приведет к непосредственной ошибке или панике.

В случае с Go, поведение при удалении элементов из карты во время итерации по ней довольно определенное и безопасное. Итератор карты в Go спроектирован так, чтобы обрабатывать изменения карты во время итерации, но это может привести к тому, что некоторые элементы будут пропущены в процессе итерации, если вы удаляете элементы впереди текущего элемента итерации.

package main

func main() {

m := map[int]string{1: "a", 2: "b", 3: "c"}

for k := range m {

if k == 2 {

delete(m, 3) // Попытка удалить ключ во время итерации

}

}

// Поведение не определено. Возможно пропуск элементов или другие непредвиденные результаты.

}

Решение.

Для безопасного удаления элементов из карты во время итераций: сначала соберите ключи элементов, которые вы хотите удалить, в отдельный слайс, а затем удалите их в отдельном цикле после завершения итерации по карте. Это гарантирует, что итерация по карте не будет нарушена изменениями карты.

package main

import "fmt"

func main() {

m := map[int]string{1: "a", 2: "b", 3: "c"}

var keysToDelete []int

for k := range m {

if k == 2 {


// добавляем ключи для удаления

keysToDelete = append(keysToDelete, 3)

}

}

// Удаляем элементы после итерации

for _, k := range keysToDelete {

delete(m, k)

}

fmt.Println(m) // Вывод: map[1:a 2:b], элемент с ключом 3 удален

}

2. Неинициализированная карта

Попытка использования неинициализированной карты вызовет панику во время выполнения.

package main

func main() {

// Попытка использования неинициализированной карты вызовет панику во время выполнения.

var m map[int]string

m[1] = "apple" // panic: assignment to entry in nil map

// Чтобы избежать этого, карта должна быть инициализирована перед использованием

m = make(map[int]string)


m[1] = "apple" // OK

}

3. Проверка наличия ключа

При получении значения из карты, если ключ не существует, возвращаемое значение будет нулевым для типа значения карты. Это может ввести в заблуждение, если не проверять, действительно ли ключ существует.

package main

import (

"fmt"

)

func main() {

m := make(map[int]int)

k := 123

val := m[1] // val == 0, ключ 123 не существует, но 0 также может быть допустимым значением

fmt.Println(val)

// Лучше использовать двухзначную форму получения элемента:

if _, ok := m[k]; !ok {

fmt.Printf("map key: %d, not found\n", k)

}

}

4. Сортировка

В Go карты не имеют внутреннего порядка, и их элементы хранятся в случайном порядке. Однако вы можете отсортировать ключи (или значения) карты, используя дополнительные шаги. Вот как можно отсортировать карту по ключам:

package main

import (

"fmt"

"sort"

)

func main() {

m := map[string]int{

"banana": 3,

"apple":  5,

"pear":  2,

"orange": 4,

}

// Создаем слайс для хранения ключей

var keys []string

// Добавляем ключи в слайс

for k := range m {

keys = append(keys, k)

}

// Сортируем ключи

sort.Strings(keys)


// Выводим отсортированную карту

for _, k := range keys {

fmt.Println(k, m[k])

}

}

5. Конкурентная модификация

Карты в Go не являются потокобезопасными, и одновременная модификация карты из разных горутин может привести к состоянию гонки и панике.

package main

import "time"

func main() {

m := make(map[int]int)

// Имитация конкурентной модификации

go func() {

for i := 0; i < 10000; i++ {

m[i] = i

}

}()

go func() {

for i := 0; i < 10000; i++ {

delete(m, i)

}

}()

go func() {

for i := 0; i < 10000; i++ {

delete(m, i)

}

}()

// Возможна паника или другое непредсказуемое поведение

time.Sleep(time.Second * 100)

}

Решение.

Для предотвращения конкурентной модификации карты и обеспечения потокобезопасности, можно использовать мьютекс из пакета sync. Мьютекс позволяет блокировать доступ к ресурсу (в данном случае к карте) для всех горутин, кроме одной, которая в данный момент владеет мьютексом. Это гарантирует, что только одна горутина может изменять карту в любой момент времени.

Пример решения с использованием мьютекса:

package main

import (

"sync"

"time"

)

func main() {

var m = make(map[int]int) // Создаем карту

var mutex sync.Mutex // Создаем мьютекс

// Горутина для добавления элементов в карту

go func() {

for i := 0; i < 10000; i++ {

mutex.Lock() // Блокируем мьютекс перед модификацией карты

m[i] = i

mutex.Unlock() // Разблокируем мьютекс после модификации карты

}

}()

// Горутина для удаления элементов из карты

go func() {

for i := 0; i < 10000; i++ {

mutex.Lock() // Блокируем мьютекс перед модификацией карты

delete(m, i)

mutex.Unlock() // Разблокируем мьютекс после модификации карты

}

}()

// Даем горутинам время на выполнение

time.Sleep(time.Second)

}

В данном примере перед каждым изменением карты мьютекс блокируется вызовом mutex.Lock(), и разблокируется вызовом mutex.Unlock() после изменения. Это гарантирует, что в любой момент времени карта может быть изменена только одной горутиной, предотвращая конкурентную модификацию и потенциальные ошибки в программе.

Использование каналов: Каналы в Go могут использоваться не только для обмена данными между горутинами, но и как средство синхронизации. Вы можете создать канал, через который будут передаваться операции чтения и записи карты, а одна горутина будет отвечать за выполнение этих операций. Это гарантирует, что все операции с картой выполняются последовательно.

package main

import (

"sync"

)

type command struct {

key  int

value int

op  string // "set", "delete", "get"

}

func main() {

var opChan = make(chan command)

var wg sync.WaitGroup

// Горутина для управления картой

go func() {

m := make(map[int]int)

for op := range opChan {

switch op.op {

case "set":

m[op.key] = op.value

case "delete":

delete(m, op.key)

}

}

}()

// Горутина для добавления элементов

wg.Add(1)

go func() {

defer wg.Done()

for i := 0; i < 100; i++ {

opChan <- command{key: i, value: i, op: "set"}

}

}()

// Горутина для удаления элементов

wg.Add(1)

go func() {

defer wg.Done()

for i := 0; i < 100; i++ {

opChan <- command{key: i, op: "delete"}

}

}()

wg.Wait()

close(opChan)

}

sync.Map: В пакете sync есть специальная структура Map, предназначенная для использования в многопоточных средах без явного использования мьютексов для синхронизации. sync.Map имеет встроенные методы для безопасной работы с данными в конкурентных ситуациях.

package main

import (

"fmt"

"sync"

)

func main() {

var m sync.Map

// Установка значения

m.Store(1, "a")

// Получение значения

if val, ok := m.Load(1); ok {

fmt.Println("Value:", val)

}

// Удаление значения

m.Delete(1)

}

Оба подхода имеют свои преимущества и недостатки. Использование каналов может быть более наглядным и понятным, но потребует больше кода для управления операциями. sync.Map более прост в использовании для простых случаев, но может быть менее гибким, чем полное управление синхронизацией через мьютексы или каналы.

Показать полностью 10
[моё] Golang Программирование Карты Длиннопост
4
FreshAngry007

Массивы и go⁠⁠

1 год назад

В Go массивы являются одной из базовых структур данных. Они представляют собой упорядоченную последовательность элементов одного типа, фиксированной длины. Важно понимать, что размер массива является частью его типа, что отличает массивы в Go от динамических слайсов, которые могут изменять свой размер во время выполнения программы.

Основные характеристики массивов в Go:

  • Фиксированный размер: Длина массива определяется при его объявлении и не может быть изменена. Это означает, что добавление элементов в массив или удаление из него невозможно без создания нового массива.

  • Однородность: Все элементы массива должны быть одного и того же типа.

  • Инициализация: Массивы могут быть инициализированы явно при объявлении или позже в коде. Также Go поддерживает инициализацию массива с помощью синтаксиса литералов.

  • Производительность: Доступ к элементам массива происходит очень быстро, так как элементы хранятся в непрерывной области памяти.

Объявление и инициализация массива:

var arr [5]int // Объявление массива из 5 целых чисел, инициализированного нулями

arr2 := [3]int{1, 2, 3} // Объявление и инициализация массива с тремя элементами

arr3 := [...]int{1, 2, 3, 4, 5} // Использование ... позволяет компилятору определить длину массива

Работа с элементами массива:

arr[0] = 10 // Присвоение значения первому элементу массива

fmt.Println(arr[1]) // Доступ к элементу массива

Передача массивов в функции:

Важно помнить, что массивы в Go передаются в функции по значению, что означает создание их копии. Для больших массивов это может быть неэффективно по памяти и времени. В таких случаях лучше использовать слайсы, которые являются ссылками на массивы.

func printArray(arr [3]int) {

for _, value := range arr {

fmt.Println(value)

}

}

myArray := [3]int{10, 20, 30}

printArray(myArray) // Передача массива в функцию

Использование массивов в Go имеет несколько нюансов и потенциальных "подводных камней", на которые стоит обратить внимание:

1. Фиксированный размер

Основное ограничение массивов заключается в их фиксированном размере. Размер массива является частью его типа, что означает, что вы должны знать размер массива на этапе компиляции. Это может быть неудобно в ситуациях, когда размер данных заранее неизвестен или может изменяться во время выполнения программы. В таких случаях лучше использовать слайсы, которые являются более гибкой альтернативой массивам.

2. Передача массива функции по значению

При передаче массива в функцию Go копирует все его элементы. Для больших массивов это может привести к значительным затратам памяти и времени выполнения. Чтобы избежать этого, рекомендуется использовать слайсы, поскольку они передаются по ссылке, что гораздо эффективнее.

3. Инициализация массивов неявными значениями

При объявлении массива без явной инициализации все его элементы инициализируются нулевыми значениями для типа элементов массива. Это поведение может быть неочевидным для начинающих и привести к ошибкам, если предполагается, что массив изначально заполнен другими значениями.

4. Ограниченная поддержка встроенных операций

В отличие от слайсов, для массивов в Go доступно меньше встроенных операций. Например, нельзя использовать функцию append для добавления элементов в массив, поскольку это изменит его размер, что невозможно для массивов.

5. Неудобство в использовании

Из-за строгих ограничений на размер и тип, работа с массивами может быть менее удобной, чем с слайсами, особенно когда требуется динамическое изменение размера коллекции или её частая передача между функциями.

Решения

Для избежания вышеупомянутых подводных камней рекомендуется использовать слайсы вместо массивов, когда это возможно. Слайсы обеспечивают большую гибкость и удобство при работе с коллекциями данных, позволяют динамически изменять размер и более эффективны с точки зрения использования памяти при передаче в функции.

Показать полностью
[моё] Программирование Golang IT Текст Массивы
0
Посты не найдены
О нас
О Пикабу Контакты Реклама Сообщить об ошибке Сообщить о нарушении законодательства Отзывы и предложения Новости Пикабу Мобильное приложение RSS
Информация
Помощь Кодекс Пикабу Команда Пикабу Конфиденциальность Правила соцсети О рекомендациях О компании
Наши проекты
Блоги Работа Промокоды Игры Курсы
Партнёры
Промокоды Биг Гик Промокоды Lamoda Промокоды Мвидео Промокоды Яндекс Маркет Промокоды Пятерочка Промокоды Aroma Butik Промокоды Яндекс Путешествия Промокоды Яндекс Еда Постила Футбол сегодня
На информационном ресурсе Pikabu.ru применяются рекомендательные технологии