---
title: Продвинутые вопросы
seo:
  title: Продвинутые вопросы — Go Developer
  description: Тема «Продвинутые вопросы» для собеседования Go Developer. Зачем используется ключевое слово defer в Go? Каков порядок возврата при использовании несколько функций с defer в рамках одной внешней функции?
---

[Все темы Go Developer](/go-developer)

## <strong>Зачем используется ключевое слово</strong> <code>defer</code> <strong>в Go?</strong> [#q-14bee738d69b81419dfef90917bad04e]

defer откладывает вызов функции до выхода из текущей функции. Функция и аргументы вычисляются при выполнении defer; сами вызовы выполняются перед возвратом в обратном порядке, в том числе при раскрутке стека из-за panic.

#### Основные характеристики&#58;

{/* prettier-ignore */}
1. <strong>Отложенное выполнение</strong>&#58;

    - Инструкции, обозначенные <code>defer</code>, выполняются в обратном порядке после завершения функции.

    - Пример&#58;

        ```go
        func example() {
            defer fmt.Println("world")
            fmt.Println("hello")
        }
        // Вывод: hello world
        ```

1. <strong>Использование для очистки</strong>&#58;

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

    - Пример&#58;

        ```go
        func readFile(filename string) {
            file, err := os.Open(filename)
            if err != nil {
                log.Fatal(err)
            }
            defer file.Close()
            // Работа с файлом
        }
        ```

1. <strong>Обратный порядок</strong>&#58;

    - Несколько вызовов <code>defer</code> выполняются в порядке LIFO (последним вошел — первым вышел).

    - Пример&#58;

        ```go
        func example() {
            defer fmt.Println("first")
            defer fmt.Println("second")
            defer fmt.Println("third")
        }
        // Вывод: third second first
        ```

:::note[Ссылки для изучения]

1. [Defer documentation. Golang](https://go.dev/tour/flowcontrol/12)
   :::

---

## <strong>Каков порядок возврата при использовании несколько функций с</strong> <code>defer</code> <strong>в рамках одной внешней функции?</strong> [#q-14bee738d69b81198e47f5507a46deae]

Когда несколько функций обозначены <code>defer</code> внутри одной функции, они выполняются в обратном порядке их объявления (порядок LIFO — последним вошел, первым вышел).

#### Пример&#58;

```go
func example() {
    defer fmt.Println("first")
    defer fmt.Println("second")
    defer fmt.Println("third")
    fmt.Println("done")
}

func main() {
    example()
}
```

#### Вывод&#58;

```go
done
third
second
first
```

#### Объяснение&#58;

1. <code>defer fmt&#46;Println&#40;&#34;first&#34;&#41;</code> — откладывается,
   выполняется последним.

1. <code>defer fmt&#46;Println&#40;&#34;second&#34;&#41;</code> — откладывается,
   выполняется предпоследним.

1. <code>defer fmt&#46;Println&#40;&#34;third&#34;&#41;</code> — откладывается,
   выполняется первым среди отложенных.

:::note[Ссылки для изучения]

1. [Defer documentation. Golang](https://go.dev/tour/flowcontrol/12)
   :::

---

## <strong>Какие бывают способы синхронизации данных в Go?</strong> [#q-14bee738d69b8183ad81e736b3c1b4bf]

Способы синхронизации данных в Go&#58;

{/* prettier-ignore */}
1. <strong>Каналы (</strong><code>Channels</code><strong>)</strong>&#58;

    - Используются для передачи данных и синхронизации между горутинами.

    - Пример&#58;

        ```go
        ch := make(chan int)
        go func() {
            ch <- 42  // Отправка данных
        }()
        val := <-ch  // Получение данных
        fmt.Println(val)
        ```

1. <strong>Мьютексы (</strong><code>Mutexes</code><strong>)</strong>&#58;

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

    - Пример&#58;

        ```go
        var mu sync.Mutex
        var counter int

        go func() {
            mu.Lock()
            counter++
            mu.Unlock()
        }()
        ```

1. <code>RWMutex</code>&#58;

    - Позволяет нескольким горутинам одновременно читать, но только одной горутине писать.

    - Пример&#58;

        ```go
        var rwmu sync.RWMutex
        var data int

        go func() {
            rwmu.RLock()
            defer rwmu.RUnlock()
            fmt.Println(data)
        }()

        go func() {
            rwmu.Lock()
            data = 42
            rwmu.Unlock()
        }()
        ```

1. <code>WaitGroup</code>&#58;

    - Используется для ожидания завершения группы горутин.

    - Пример&#58;

        ```go
        var wg sync.WaitGroup
        wg.Add(1)

        go func() {
            defer wg.Done()
            fmt.Println("Goroutine finished")
        }()

        wg.Wait()
        ```

1. <code>Cond</code>&#58;

    - Условные переменные для блокировки и ожидания определенных условий.

    - Пример&#58;

        ```go
        var mu sync.Mutex
        cond := sync.NewCond(&mu)
        ready := false
        done := make(chan struct{})

        go func() {
            defer close(done)
            mu.Lock()
            for !ready {
                cond.Wait()
            }
            fmt.Println("Condition met")
            mu.Unlock()
        }()

        mu.Lock()
        ready = true
        cond.Signal()
        mu.Unlock()
        <-done
        ```

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

:::note[Ссылки для изучения]

1. [Примитивы синхронизации в Go](https://habr.com/ru/companies/yadro/articles/799281/)
   :::

---

## <strong>Что такое</strong> <code>mutex</code><strong>, какие они бывают и как их использовать?</strong> [#q-14bee738d69b8157965dce4c1d851b03]

<code>Mutex</code> (Mutual Exclusion) — это примитив синхронизации, используемый
для защиты разделяемых ресурсов от одновременного доступа нескольких горутин.

#### Виды мьютексов&#58;

{/* prettier-ignore */}
1. <code>sync&#46;Mutex</code>&#58;

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

    - Пример&#58;

        ```go
        var mu sync.Mutex
        var counter int

        func increment() {
            mu.Lock()   // Захват мьютекса
            counter++   // Критическая секция
            mu.Unlock() // Освобождение мьютекса
        }
        ```

1. <code>sync&#46;RWMutex</code>&#58;

    - Позволяет нескольким горутинам одновременно читать ресурс, но только одной горутине писать.

    - Пример&#58;

        ```go
        var rwmu sync.RWMutex
        var data int

        func readData() {
            rwmu.RLock()   // Захват мьютекса для чтения
            fmt.Println(data)
            rwmu.RUnlock() // Освобождение мьютекса для чтения
        }

        func writeData() {
            rwmu.Lock()   // Захват мьютекса для записи
            data = 42
            rwmu.Unlock() // Освобождение мьютекса для записи
        }
        ```

#### Использование мьютексов&#58;

{/* prettier-ignore */}
1. <code>sync&#46;Mutex</code>&#58;

    - <strong>Lock()</strong>&#58; Захватывает мьютекс. Если мьютекс уже захвачен, текущая горутина блокируется до освобождения мьютекса.

    - <strong>Unlock()</strong>&#58; Освобождает мьютекс. Если другие горутины ждут мьютекса, одна из них будет разблокирована.

1. <code>sync&#46;RWMutex</code>&#58;

    - <strong>RLock()</strong>&#58; Захватывает мьютекс для чтения. Несколько горутин могут одновременно захватывать мьютекс для чтения.

    - <strong>RUnlock()</strong>&#58; Освобождает мьютекс для чтения.

    - <strong>Lock()</strong>&#58; Захватывает мьютекс для записи. Блокирует другие горутины от захвата мьютекса для чтения или записи.

    - <strong>Unlock()</strong>&#58; Освобождает мьютекс для записи.

:::note[Ссылки для изучения]

1. [Mutex в Go](https://metanit.com/go/tutorial/7.6.php)
   :::

---

## <strong>Что такое</strong> <code>atomics</code><strong>, какие бывают и как и когда их лучше использовать?</strong> [#q-14bee738d69b810b8f02e838e6290b62]

<code>Atomics</code> — это примитивы синхронизации, которые обеспечивают
атомарные операции над переменными, позволяя избежать использования мьютексов
для простых операций.

#### Виды атомарных операций&#58;

{/* prettier-ignore */}
1. <strong>Базовые типы</strong>&#58;

    - <code>sync&#47;atomic</code> пакет предоставляет атомарные операции для следующих типов&#58;

        - <code>int32</code>, <code>int64</code>

        - <code>uint32</code>, <code>uint64</code>

        - <code>uintptr</code>

        - <code>unsafe&#46;Pointer</code>

1. <strong>Основные функции</strong>&#58;

    - <code>Add</code>&#58; Атомарное добавление.

        ```go
        atomic.AddInt32(&i, 1)
        ```

    - <code>Load</code>&#58; Атомарное чтение.

        ```go
        value := atomic.LoadInt32(&i)
        ```

    - <code>Store</code>&#58; Атомарная запись.

        ```go
        atomic.StoreInt32(&i, 42)
        ```

    - <code>Swap</code>&#58; Атомарный обмен.

        ```go
        old := atomic.SwapInt32(&i, 42)
        ```

    - <code>CompareAndSwap</code>&#58; Атомарное сравнение и обмен.

        ```go
        swapped := atomic.CompareAndSwapInt32(&i, old, new)
        ```

#### Когда использовать <code>atomics</code>&#58;

{/* prettier-ignore */}
1. <strong>Простые операции</strong>&#58;

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

    - Пример&#58; счетчик запросов, флаги состояния.

1. <strong>Высокая производительность</strong>&#58;

    - Атомарные операции полезны для отдельных счетчиков и флагов. Их преимущество по скорости зависит от нагрузки и конкуренции; несколько атомарных операций не превращают составное действие в одну атомарную операцию.

:::note[Ссылки для изучения]

1. [Атомики в Go](https://habr.com/ru/articles/744822/)
   :::

---

## <strong>Что такое</strong> <code>sync&#46;Map</code><strong>?</strong> [#q-14bee738d69b816f9330ce83b7f8d479]

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

#### Основные характеристики&#58;

{/* prettier-ignore */}
1. <strong>Потокобезопасность</strong>&#58;

    - <code>sync&#46;Map</code> автоматически обеспечивает безопасность при доступе из нескольких горутин.

1. <strong>Методы</strong>&#58;

    - <code>Store</code>&#58; Сохраняет значение по ключу.

        ```go
        var m sync.Map
        m.Store("key", "value")
        ```

    - <code>Load</code>&#58; Получает значение по ключу.

        ```go
        value, ok := m.Load("key")
        ```

    - <code>LoadOrStore</code>&#58; Возвращает существующее значение по ключу или сохраняет и возвращает новое, если ключа не было.

        ```go
        actual, loaded := m.LoadOrStore("key", "newValue")
        ```

    - <code>Delete</code>&#58; Удаляет значение по ключу.

        ```go
        m.Delete("key")
        ```

    - <code>Range</code>&#58; Итерирует по всем ключам и значениям в map.

        ```go
        m.Range(func(key, value interface{}) bool {
            fmt.Println(key, value)
            return true
        })
        ```

#### Когда использовать sync.Map&#58;

{/* prettier-ignore */}
1. <strong>Частое чтение и запись</strong>&#58;

    - Выбирайте sync.Map по профилю доступа и измерениям, а не только по частоте чтений и записей.

1. <strong>Кэширование и конфигурации</strong>&#58;

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

:::note[Ссылки для изучения]

1. [Гайд по sync.Map](https://golangforall.com/ru/post/sync-map.html)
   :::

---

## <strong>Что такое</strong> <code>lock&#45;free</code> <strong>структуры данных, и есть ли в Go такие?</strong> [#q-14bee738d69b81bab7d9f424dcf5b588]

<code>Lock&#45;free</code> <strong>структуры данных</strong> — это структуры
данных, которые обеспечивают безопасный доступ из нескольких потоков без
использования блокировок (мьютексов). Они используют атомарные операции для
обеспечения согласованности данных и предотвращения гонок данных.

#### Преимущества&#58;

1. <strong>Высокая производительность</strong>&#58; Избегание блокировок
   позволяет избежать затрат на переключение контекста и уменьшить задержки.

1. <strong>Отсутствие взаимоблокировок</strong>&#58; Исключается возможность
   взаимоблокировок (deadlocks), так как блокировки не используются.

#### Lock-free структуры данных в Go&#58;

В Go есть несколько примитивов и пакетов, которые поддерживают lock-free доступ&#58;

{/* prettier-ignore */}
1. <strong>sync/atomic</strong>&#58;

    - Обеспечивает атомарные операции над примитивными типами данных (например, <code>int32</code>, <code>int64</code>, <code>uint32</code>, <code>uint64</code>, <code>uintptr</code>, <code>unsafe&#46;Pointer</code>).

    - Пример&#58;

        ```go
        import "sync/atomic"

        var counter int32
        atomic.AddInt32(&counter, 1)
        ```

1. <strong>sync.Map</strong>&#58;

    - Хотя не является строго lock-free, <code>sync&#46;Map</code> использует оптимизированные алгоритмы, минимизирующие блокировки и обеспечивающие высокую производительность в условиях высокой конкуренции.

    - Пример&#58;

        ```go
        import "sync"

        var m sync.Map
        m.Store("key", "value")
        value, ok := m.Load("key")
        ```

:::note[Ссылки для изучения]

1. [Что такое lock-free в Go](https://easyoffer.ru/question/6017)
   :::

---

## Как обработать панику с помощью defer и recover? [#q-14bee738d69b810ba1f6f719af39380b]

Для обработки паники в Go используются <code>defer</code> и <code>recover</code>. <code>defer</code> откладывает выполнение функции до завершения текущей функции, а <code>recover</code> перехватывает панику, если она произошла.

#### Пример использования&#58;

```go
package main

import (
    "fmt"
)

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()

    // Функция, вызывающая панику
    panic("Something went wrong!")
    fmt.Println("This will not be printed")
}
```

#### Порядок выполнения&#58;

{/* prettier-ignore */}
1. <strong>defer</strong>&#58;

    - Функция, отложенная с помощью <code>defer</code>, выполняется в конце функции <code>main</code>, даже если в ней возникла паника.

1. <strong>recover</strong>&#58;

    - recover останавливает панику при прямом вызове из отложенной функции в той же горутине. Исполнение не возвращается к строке после panic&#58; функция с обработчиком завершается обычным возвратом.

:::note[Ссылки для изучения]

1. [Defer documentation. Golang](https://go.dev/tour/flowcontrol/12)
   :::

---

## <strong>Что такое</strong> <code>context</code> <strong>в Go? Какие бывают</strong> <code>context</code> <strong>в Go?</strong> [#q-14bee738d69b814aa313c4b453c86c5c]

<code>Context</code> — это стандартный механизм для передачи значений, сигналов
отмены и дедлайнов между горутинами.

#### Виды context&#58;

{/* prettier-ignore */}
1. <code>context&#46;Background&#40;&#41;</code>&#58;

    - Базовый пустой контекст, не имеющий значений и сигналов отмены.

    - Используется как корневой контекст.

1. <code>context&#46;TODO&#40;&#41;</code>&#58;

    - Контекст-заглушка, который используется, когда контекст еще не определен.

    - Указывает, что работа над определением контекста еще не завершена.

1. <code>context&#46;WithCancel&#40;parent Context&#41;</code>&#58;

    - Создает дочерний контекст с возможностью отмены.

    - Функция <code>cancel</code> может быть вызвана для отмены контекста.

    - Пример&#58;

        ```go
        ctx, cancel := context.WithCancel(context.Background())
        defer cancel()
        ```

1. <code>context&#46;WithDeadline&#40;parent Context&#44; d time&#46;Time&#41;</code>&#58;

    - Создает дочерний контекст, который автоматически отменяется по истечении заданного дедлайна.

    - Пример&#58;

        ```go
        ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
        defer cancel()
        ```

1. <code>context&#46;WithTimeout&#40;parent Context&#44; timeout time&#46;Duration&#41;</code>&#58;

    - Создает дочерний контекст, который автоматически отменяется по истечении заданного таймаута.

    - Пример&#58;

        ```go
        ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
        defer cancel()
        ```

1. <code>context&#46;WithValue&#40;parent Context&#44; key&#44; val interface&#123;&#125;&#41;</code>&#58;

    - Создает дочерний контекст, который несет значения ключ-значение.

    - Пример&#58;

        ```go
        ctx := context.WithValue(context.Background(), "key", "value")
        ```

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

:::note[Ссылки для изучения]

1. [Пакет context в Go](https://habr.com/ru/companies/pt/articles/764850/)
   :::

---

---

## <strong>Как передаются параметры в функцию по указателю или по значению?</strong> [#q-14bee738d69b81c083f8f71a48436997]

Передача параметров в функцию в Go&#58;

{/* prettier-ignore */}
1. <strong>По значению</strong>&#58;

    - Параметры передаются по значению, то есть функция получает копию аргумента.

    - Пример&#58;

        ```go
        func modifyValue(val int) {
            val = 10
        }

        func main() {
            a := 5
            modifyValue(a)
            fmt.Println(a)  // Вывод: 5 (значение не изменилось)
        }
        ```

1. <strong>По указателю</strong>&#58;

    - Go всегда передает аргументы по значению. При передаче указателя копируется его значение — адрес; через эту копию функция может изменить объект по адресу.

    - Пример&#58;

        ```go
        func modifyValue(ptr *int) {
            *ptr = 10
        }

        func main() {
            a := 5
            modifyValue(&a)
            fmt.Println(a)  // Вывод: 10 (значение изменилось)
        }
        ```

:::note[Ссылки для изучения]

1. [Передача значений VS передача указателей в Go](https://habr.com/ru/companies/it-guide/articles/744046/)
   :::

---

## <strong>Что такое пакеты (</strong><code>package</code><strong>) в Go?</strong> [#q-14bee738d69b81b2b870d90a04ddffaf]

<strong>Пакеты</strong> — это основной способ организации и разделения кода в
Go. Они позволяют группировать связанные функции, типы и переменные в отдельные
единицы, обеспечивая модульность и повторное использование кода.

#### Основные характеристики&#58;

{/* prettier-ignore */}
1. <strong>Определение пакета</strong>&#58;

    - Каждый файл Go начинается с объявления пакета.

    - Пример&#58;

        ```go
        package main
        ```

1. <strong>Главный пакет</strong>&#58;

    - Пакет <code>main</code> — это точка входа для выполнения программы. Он должен содержать функцию <code>main</code>.

    - Пример&#58;

        ```go
        package main

        import "fmt"

        func main() {
            fmt.Println("Hello, Go!")
        }
        ```

1. <strong>Импорт пакетов</strong>&#58;

    - Для использования функций и типов из других пакетов их нужно импортировать.

    - Пример&#58;

        ```go
        package main

        import (
            "fmt"
            "math"
        )

        func main() {
            fmt.Println(math.Pi)
        }
        ```

1. <strong>Создание собственного пакета</strong>&#58;

    - Можно создать собственные пакеты для организации кода.

    - Пример&#58;

        ```go
        // package mypackage
        package mypackage

        func MyFunction() {
            fmt.Println("Hello from my package!")
        }
        ```

1. <strong>Имя пакета</strong>&#58;

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

#### Пример использования собственного пакета&#58;

```go
// Файл mypackage/mypackage.go
package mypackage

import "fmt"

func MyFunction() {
    fmt.Println("Hello from my package!")
}

// Файл main.go
package main

import (
    "mypackage"
)

func main() {
    mypackage.MyFunction()
}
```

:::note[Ссылки для изучения]

1. [Как писать пакеты в Go](https://habr.com/ru/companies/ruvds/articles/464289/)
   :::

---

## <strong>Можно ли реализовать</strong> <code>sync&#46;Mutex</code> <strong>и</strong> <code>sync&#46;WaitGroup</code> <strong>на каналах? Как?</strong> [#q-14bee738d69b81efb041e14dba5c12a1]

Реализация <code>sync&#46;Mutex</code> на каналах&#58;

```go
type Mutex struct {
    ch chan struct{}
}

func NewMutex() *Mutex {
    m := &Mutex{ch: make(chan struct{}, 1)}
    m.ch <- struct{}{}
    return m
}

func (m *Mutex) Lock() {
    <-m.ch
}

func (m *Mutex) Unlock() {
    m.ch <- struct{}{}
}
```

Реализация <code>sync&#46;WaitGroup</code> на каналах&#58;

```go
// Одноразовое ожидание известного числа задач на канале.
// Это не полная замена API sync.WaitGroup.
func runWorkers(n int, work func()) {
    done := make(chan struct{}, n)
    for i := 0; i < n; i++ {
        go func() {
            work()
            done <- struct{}{}
        }()
    }
    for i := 0; i < n; i++ {
        <-done
    }
}
```

:::note[Ссылки для изучения]

1. [Мьютексы в Go](https://metanit.com/go/tutorial/7.6.php)
   :::

## <strong>Что такое указатели?</strong> [#q-14bee738d69b812fb1bafe9a45a4c678]

<strong>Указатели</strong> — это переменные, которые хранят адреса других
переменных.

#### Основные характеристики&#58;

{/* prettier-ignore */}
1. <strong>Создание указателя</strong>&#58;

    - Указатель на переменную создается с помощью оператора <code>&#38;</code>.

    - Пример&#58;

        ```go
        var a int = 42
        var p *int = &a  // p теперь указывает на a
        ```

1. <strong>Разыменование указателя</strong>&#58;

    - Доступ к значению по указателю выполняется оператором разыменования \*&#58; например, \*p.

    - Пример&#58;

        ```go
        fmt.Println(*p)  // Вывод: 42
        ```

1. <strong>Изменение значения через указатель</strong>&#58;

    - Изменение значения переменной через указатель.

    - Пример&#58;

        ```go
        *p = 21
        fmt.Println(a)  // Вывод: 21
        ```

#### Пример использования&#58;

```go
package main

import "fmt"

func main() {
    var a int = 42
    var p *int = &a  // Создание указателя на a

    fmt.Println(a)  // Вывод: 42
    fmt.Println(*p) // Вывод: 42

    *p = 21  // Изменение значения через указатель
    fmt.Println(a)  // Вывод: 21
}
```

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

:::note[Ссылки для изучения]

1. [Передача значений VS передача указателей в Go](https://habr.com/ru/companies/it-guide/articles/744046/)
   :::
