Golang | Вопросы собесов
4.34K subscribers
27 photos
702 links
Download Telegram
🤔 Как устроены строки в Go

Строки в Go являются неизменяемыми последовательностями байтов, хранящими текст в формате UTF-8. Каждая строка представляет собой структуру, содержащую указатель на байты и длину, что позволяет эффективно работать с текстом и его подстроками. Из-за неизменяемости строки при необходимости создания новой строки приходится выделять новую память.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Безопасен ли map?

Нет, карты (maps) в Go не являются потокобезопасными по умолчанию. Использование карты в нескольких горутинах без должной синхронизации может привести к состояниям гонки, некорректным данным и паникам. Давайте рассмотрим более детально, почему это так и как можно обеспечить потокобезопасность при работе с картами в многопоточной среде.

🚩Почему карты в Go не потокобезопасны

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

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

🚩Подходы к обеспечению потокобезопасности

🟠Мьютексы (Mutex)
Самый распространенный способ синхронизации доступа к карте — использование sync.Mutex. Мьютексы позволяют заблокировать доступ к карте на время чтения или записи.
package main

import (
"fmt"
"sync"
)

func main() {
var mu sync.Mutex
m := make(map[string]int)
var wg sync.WaitGroup

write := func(key string, value int) {
mu.Lock()
m[key] = value
mu.Unlock()
}

read := func(key string) int {
mu.Lock()
defer mu.Unlock()
return m[key]
}

wg.Add(2)
go func() {
defer wg.Done()
write("key1", 42)
}()
go func() {
defer wg.Done()
fmt.Println(read("key1"))
}()
wg.Wait()
}


🟠RWMutex
Если в вашей программе чаще происходят операции чтения, чем записи, можно использовать sync.RWMutex, который позволяет нескольким горутинам читать данные одновременно, но обеспечивает эксклюзивный доступ для записи.
package main

import (
"fmt"
"sync"
)

func main() {
var mu sync.RWMutex
m := make(map[string]int)
var wg sync.WaitGroup

write := func(key string, value int) {
mu.Lock()
m[key] = value
mu.Unlock()
}

read := func(key string) int {
mu.RLock()
defer mu.RUnlock()
return m[key]
}

wg.Add(2)
go func() {
defer wg.Done()
write("key1", 42)
}()
go func() {
defer wg.Done()
fmt.Println(read("key1"))
}()
wg.Wait()
}


🟠sync.Map
Go предоставляет специальную структуру sync.Map, которая изначально создана для безопасного использования в многопоточной среде. Она автоматически обеспечивает синхронизацию операций.
package main

import (
"fmt"
"sync"
)

func main() {
var m sync.Map
var wg sync.WaitGroup

wg.Add(2)
go func() {
defer wg.Done()
m.Store("key1", 42)
}()
go func() {
defer wg.Done()
value, _ := m.Load("key1")
fmt.Println(value)
}()
wg.Wait()
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Чем горутины отличаются от тредов

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Насколько безопасны каналы?

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

🚩Потокобезопасность каналов

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

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

import (
"fmt"
"sync"
)

func main() {
ch := make(chan int)
var wg sync.WaitGroup

// Горутина для отправки данных
wg.Add(1)
go func() {
defer wg.Done()
ch <- 42
}()

// Горутина для получения данных
wg.Add(1)
go func() {
defer wg.Done()
value := <-ch
fmt.Println(value)
}()

wg.Wait()
}


🟠Закрытие канала
Закрытие канала должно выполняться только одной горутиной. Попытка закрытия канала из нескольких горутин может привести к панике.
package main

func main() {
ch := make(chan int)

// Правильный способ закрытия канала
go func() {
close(ch)
}()

// Неправильный способ (может вызвать панику)
// go func() {
// close(ch)
// }()
}


🟠Отправка в закрытый канал
Отправка данных в закрытый канал вызывает панику, поэтому необходимо быть уверенным, что канал не закрыт перед отправкой данных.
package main

func main() {
ch := make(chan int)
close(ch)

// Попытка отправки данных в закрытый канал вызовет панику
// ch <- 42
}


🟠Чтение из закрытого канала
Чтение из закрытого канала безопасно. При чтении из закрытого канала, если канал пуст, будет возвращено нулевое значение типа и флаг, указывающий на закрытие канала.
package main

import "fmt"

func main() {
ch := make(chan int)
close(ch)

value, ok := <-ch
fmt.Println(value, ok) // Вывод: 0 false
}


🚩Практики безопасного использования каналов

🟠Использование буферизованных каналов
Буферизованные каналы позволяют отправлять несколько значений без блокировки, пока буфер не заполнится, что может улучшить производительность.
package main

import "fmt"

func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2

fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
}


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

🟠Правильное планирование закрытия каналов
Если канал должен быть закрыт, делайте это только после того, как все отправляющие горутины завершат свою работу.
package main

import "fmt"

func main() {
ch := make(chan int)

go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()

for value := range ch {
fmt.Println(value)
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как завершить много горутин

Для завершения множества горутин обычно используется канал (например, done), через который можно отправить сигнал для завершения работы. Также можно использовать контексты (context.Context) для отмены, чтобы горутины могли проверять его состояние и корректно завершаться при получении сигнала отмены. Такой подход обеспечивает упорядоченное и безопасное завершение множества горутин.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что следует помнить при работе с мьютексами в Go?

При работе с мьютексами в Go балансируйте Lock и Unlock (используйте defer), минимизируйте время удержания мьютекса и применяйте RLock для чтения в sync.RWMutex для повышения параллелизма.

🚩Основные принципы работы с мьютексами

🟠Использование мьютексов для защиты общих ресурсов
Мьютексы (sync.Mutex) предназначены для обеспечения эксклюзивного доступа к общим ресурсам. Используйте мьютекс, чтобы защитить критические секции кода, которые обращаются к общим данным.
var mu sync.Mutex
var sharedResource int

func updateResource(value int) {
mu.Lock()
sharedResource = value
mu.Unlock()
}


🟠Lock и Unlock
Всегда вызывайте mu.Lock() перед доступом к общему ресурсу и mu.Unlock() сразу после завершения работы с ним. Это гарантирует, что доступ к ресурсу будет синхронизирован.
mu.Lock()
// доступ к общему ресурсу
sharedResource = 42
mu.Unlock()


🟠Использование `defer` для разблокировки
Используйте defer для вызова mu.Unlock() сразу после блокировки. Это поможет избежать забывчивости в случае возврата из функции или возникновения ошибок.
mu.Lock()
defer mu.Unlock()
sharedResource = 42


🟠Избегайте взаимных блокировок (deadlocks)
Взаимные блокировки могут возникать, если несколько горутин заблокированы, ожидая освобождения мьютексов друг от друга. Чтобы избежать этого, всегда соблюдайте единый порядок блокировок и разблокировок.
var mu1, mu2 sync.Mutex

func funcA() {
mu1.Lock()
defer mu1.Unlock()
mu2.Lock()
defer mu2.Unlock()
// доступ к ресурсам, защищенным mu1 и mu2
}

func funcB() {
mu2.Lock()
defer mu2.Unlock()
mu1.Lock()
defer mu1.Unlock()
// доступ к ресурсам, защищенным mu1 и mu2
}


🟠Минимизируйте время блокировки
Старайтесь минимизировать время, в течение которого держится блокировка, чтобы уменьшить вероятность конфликтов и повысить производительность.
mu.Lock()
// Минимально необходимый код для работы с защищенным ресурсом
temp := sharedResource
mu.Unlock()

// Дальнейшая обработка вне критической секции
process(temp)


🟠Не используйте мьютексы для длительных операций
Избегайте выполнения длительных операций внутри критической секции, чтобы не блокировать другие горутины.
mu.Lock()
temp := sharedResource
mu.Unlock()

// Выполнение длительных операций вне блокировки
longRunningOperation(temp)


🟠Избегайте двойной блокировки
Никогда не пытайтесь заблокировать мьютекс, который уже заблокирован текущей горутиной, так как это приведет к взаимной блокировке.
mu.Lock()
// Код
// mu.Lock() // Это приведет к взаимной блокировке
mu.Unlock()


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

Несколько горутин безопасно увеличивают общий счетчик с использованием мьютекса.
package main

import (
"fmt"
"sync"
)

var (
counter int
mu sync.Mutex
wg sync.WaitGroup
)

func increment() {
defer wg.Done()
for i := 0; i < 1000; i++ {
mu.Lock()
counter++
mu.Unlock()
}
}

func main() {
wg.Add(2)
go increment()
go increment()
wg.Wait()
fmt.Println("Final Counter:", counter)
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что делает команда килл в linux?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Расскажи про аналог default в select"e?

В Go конструкция select используется для обработки операций с каналами. Она позволяет горутинам ждать событий на нескольких каналах одновременно. Аналогом конструкции default в select является опция, позволяющая избежать блокировки, если ни один из каналов не готов.

🚩Как работает `select` с `default`

Когда select выполняется, он блокируется до тех пор, пока один из его case-блоков не станет готов к выполнению (т.е. канал не станет доступным для отправки или получения данных). Однако, если добавить блок default, select может выполнить его немедленно, если ни один из других case-блоков не готов. Это позволяет избежать блокировки горутины.

🚩Пример использования `default` в `select`

Мы используем select с default, чтобы выполнить неблокирующие операции с каналами.
package main

import (
"fmt"
"time"
)

func main() {
ch1 := make(chan int)
ch2 := make(chan int)

go func() {
time.Sleep(2 * time.Second)
ch1 <- 1
}()

go func() {
time.Sleep(1 * time.Second)
ch2 <- 2
}()

for i := 0; i < 3; i++ {
select {
case val := <-ch1:
fmt.Println("Received from ch1:", val)
case val := <-ch2:
fmt.Println("Received from ch2:", val)
default:
fmt.Println("No channel is ready")
time.Sleep(500 * time.Millisecond)
}
}
}


🟠Мы создали два канала ch1 и ch2.
🟠Две горутины отправляют значения в эти каналы с задержкой.
🟠В основном цикле используется select с default, который проверяет готовность каналов каждые 500 миллисекунд.
🟠Если ни один из каналов не готов, выполняется блок default, выводящий сообщение "No channel is ready".

🚩Когда использовать `default` в `select`

🟠Неблокирующие операции
Если вы хотите проверить наличие данных на канале или возможность отправки данных без блокировки, используйте default.
🟠Избежание дедлоков
В некоторых случаях default может предотвратить дедлоки, позволяя горутине продолжить выполнение, даже если каналы временно недоступны.
🟠Проверка состояния канала
Можно использовать default для периодической проверки состояния канала или выполнения задач в ожидании готовности каналов.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое утиная типизация?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Зачем нужны WaitGroup?

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

🚩Основные функции WaitGroup

🟠Add(delta int)
Увеличивает (или уменьшает) счетчик горутин на заданное значение delta.
Обычно вызывается до запуска горутин, чтобы установить количество горутин, которые нужно дождаться.
🟠Done()
Уменьшает счетчик горутин на 1.
Вызывается горутиной, когда она завершает свою работу.
🟠Wait()
Блокирует выполнение до тех пор, пока счетчик горутин не станет равен нулю.
Обычно вызывается основной горутиной для ожидания завершения всех горутин.

🚩Пример использования `WaitGroup`

Мы используем WaitGroup для ожидания завершения нескольких горутин.
package main

import (
"fmt"
"sync"
"time"
)

func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Уменьшает счетчик на 1 при завершении работы горутины
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}

func main() {
var wg sync.WaitGroup

for i := 1; i <= 5; i++ {
wg.Add(1) // Увеличивает счетчик горутин на 1
go worker(i, &wg)
}

wg.Wait() // Ожидает завершения всех горутин
fmt.Println("All workers done")
}


🟠Мы создаем 5 горутин, каждая из которых выполняет функцию worker.
🟠Счетчик WaitGroup увеличивается на 1 перед запуском каждой горутины с помощью wg.Add(1).
🟠Каждая горутина вызывает wg.Done() при завершении, уменьшая счетчик на 1.
🟠Основная горутина вызывает wg.Wait(), блокируясь до тех пор, пока все горутины не завершат свою работу.

🚩Почему `WaitGroup` необходимы

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

🚩Пример с ошибкой без `WaitGroup`

Без использования WaitGroup основной поток может завершиться до завершения всех горутин, что приведет к неполной обработке данных. В этом примере использование time.Sleep для ожидания является ненадежным и не гарантирует завершение всех горутин. Вместо этого правильное использование WaitGroup обеспечивает корректное завершение всех задач.
package main

import (
"fmt"
"time"
)

func worker(id int) {
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}

func main() {
for i := 1; i <= 5; i++ {
go worker(i)
}

time.Sleep(2 * time.Second) // Это не гарантирует завершение всех горутин
fmt.Println("All workers done")
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В чем отличия http 1.1 и http 2?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что знаешь о sync.map?

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

🚩Основные особенности `sync.Map`

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

🚩Основные методы `sync.Map`

🟠Store(key, value interface{})
Сохраняет значение по заданному ключу. Если ключ уже существует, его значение будет перезаписано.
var m sync.Map
m.Store("foo", 1)


🟠Load(key interface{}) (value interface{}, ok bool):
Возвращает значение, сохраненное по заданному ключу, и булев флаг, указывающий, было ли найдено значение.
value, ok := m.Load("foo")
if ok {
fmt.Println("Found:", value)
} else {
fmt.Println("Not found")
}


🟠LoadOrStore(key, value interface{}) (actual interface{}, loaded bool):
Возвращает существующее значение по ключу, если ключ уже существует, иначе сохраняет и возвращает новое значение. Булев флаг указывает, существовал ли ключ до вызова.
actual, loaded := m.LoadOrStore("foo", 1)
if loaded {
fmt.Println("Existing value:", actual)
} else {
fmt.Println("Stored new value:", actual)
}


🟠Delete(key interface{}):
Удаляет значение по заданному ключу.
m.Delete("foo")


🟠Range(f func(key, value interface{}) bool):
Выполняет функцию для каждого ключа и значения в карте. Если функция возвращает false, обход прекращается.
   m.Store("foo", 1)
m.Store("bar", 2)

m.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true // продолжить обход
})


🚩Пример использования `sync.Map`

Несколько горутин безопасно работают с sync.Map.
package main

import (
"fmt"
"sync"
)

func main() {
var m sync.Map
var wg sync.WaitGroup

// Запись в sync.Map из нескольких горутин
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
m.Store(i, i*i)
}(i)
}

wg.Wait()

// Чтение из sync.Map
m.Range(func(key, value interface{}) bool {
fmt.Printf("key: %v, value: %v\n", key, value)
return true
})
}


🚩Когда использовать `sync.Map`

🟠Частое чтение и редкие записи:
sync.Map оптимизирована для сценариев с частыми операциями чтения и редкими операциями записи. Она может быть менее эффективной при частых изменениях структуры карты.
🟠Замена мьютексов и карт:
Если нужно использовать карту в многопоточной среде и не хочется вручную управлять блокировками с помощью мьютексов.
🟠Понятный и простой код:
sync.Map может сделать код более понятным и простым за счет использования высокоуровневых методов вместо ручного управления блокировками.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Разница между WHERE и HAVING?

WHERE фильтрует строки до выполнения группировки данных, а HAVING — после. WHERE используется для условий над исходными данными, а HAVING — для агрегатных функций. Эти операторы работают на разных этапах выполнения SQL-запроса.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как объединить по смыслу Маппу и Мьютексы?

Объединение карты (map) и мьютексов (mutexes) в Go — это стандартный способ обеспечения потокобезопасности при доступе к данным в многопоточной среде. Это позволяет использовать карту для хранения данных и мьютексы для синхронизации доступа к этим данным.

🚩Основные шаги для объединения карты и мьютексов

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

🚩Пример реализации

Мы создадим потокобезопасную карту с использованием мьютекса.
package main

import (
"fmt"
"sync"
)

// SafeMap структура, содержащая карту и мьютекс для синхронизации
type SafeMap struct {
mu sync.Mutex
m map[string]int
}

// NewSafeMap создает новый SafeMap
func NewSafeMap() *SafeMap {
return &SafeMap{
m: make(map[string]int),
}
}

// Store сохраняет значение в карту
func (sm *SafeMap) Store(key string, value int) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.m[key] = value
}

// Load возвращает значение из карты
func (sm *SafeMap) Load(key string) (int, bool) {
sm.mu.Lock()
defer sm.mu.Unlock()
value, ok := sm.m[key]
return value, ok
}

// Delete удаляет значение из карты
func (sm *SafeMap) Delete(key string) {
sm.mu.Lock()
defer sm.mu.Unlock()
delete(sm.m, key)
}

// Range выполняет функцию для каждого ключа и значения в карте
func (sm *SafeMap) Range(f func(key string, value int) bool) {
sm.mu.Lock()
defer sm.mu.Unlock()
for k, v := range sm.m {
if !f(k, v) {
break
}
}
}

func main() {
safeMap := NewSafeMap()

var wg sync.WaitGroup

// Запись в SafeMap из нескольких горутин
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key%d", i)
safeMap.Store(key, i*i)
}(i)
}

wg.Wait()

// Чтение из SafeMap
safeMap.Range(func(key string, value int) bool {
fmt.Printf("key: %s, value: %d\n", key, value)
return true
})
}


1⃣Создание структуры `SafeMap`
Структура SafeMap содержит мьютекс mu и карту m, что позволяет синхронизировать доступ к карте.

2⃣Методы `Store`, `Load`, `Delete`, `Range`
Каждый метод сначала блокирует мьютекс mu для обеспечения эксклюзивного доступа к карте, а затем выполняет соответствующую операцию (сохранение, загрузка, удаление, обход). Метод Range позволяет выполнить функцию для каждого ключа и значения в карте, обеспечивая синхронизацию на время обхода.

🚩Преимущества объединения карты и мьютексов

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

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В чем разница между императивным и декларативным?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔Чем отличается RW Мьютекс от обычного?

RWMutex (Read-Write Mutex) позволяет разделять доступ для чтения и блокировать только для записи, в отличие от обычного Mutex, который блокирует как чтение, так и запись. С помощью RWMutex можно использовать RLock для параллельного доступа к данным в режиме чтения, что увеличивает производительность, если много горутин читают данные и редко их изменяют. Для записи используется Lock, который блокирует доступ к данным для всех других операций чтения и записи.

🚩Обычный мьютекс (sync.Mutex)

sync.Mutex используется для обеспечения эксклюзивного доступа к ресурсу для всех операций (чтение и запись). Подходит для сценариев с частыми операциями записи или равномерным распределением чтений и записей.
var mu sync.Mutex
var counter int

func increment() {
mu.Lock()
counter++
mu.Unlock()
}

func main() {
var wg sync.WaitGroup

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

wg.Wait()
fmt.Println("Counter:", counter)
}


🚩RW мьютекс (sync.RWMutex)

sync.RWMutex предоставляет два типа блокировок: для чтения (RLock) и для записи (Lock). Позволяет нескольким горутинам одновременно читать данные, если нет активных блокировок для записи. Улучшает производительность в сценариях с частыми чтениями и редкими записями.
var rwMu sync.RWMutex
var counter int

func readCounter() int {
rwMu.RLock()
defer rwMu.RUnlock()
return counter
}

func increment() {
rwMu.Lock()
counter++
rwMu.Unlock()
}

func main() {
var wg sync.WaitGroup

for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Read Counter:", readCounter())
}()
}

for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}

wg.Wait()
fmt.Println("Final Counter:", counter)
}


🚩Сравнение

🟠Эксклюзивность
sync.Mutex блокирует доступ для всех операций.
sync.RWMutex позволяет одновременное чтение, но блокирует доступ при записи.

🟠Производительность
sync.Mutex подходит для частых записей или равномерного распределения операций.
sync.RWMutex подходит для частых чтений и редких записей.

🟠Применение:
Используйте sync.Mutex для эксклюзивного доступа без различий между чтением и записью.
Используйте sync.RWMutex для улучшения производительности при частых чтениях.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое эвакуация, и в каком случае она будет происходить?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Расскажи, что такое starvation/голодание?

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

🚩Основные причины голодания

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

🚩Пример голодания

Один поток постоянно захватывает мьютекс, не давая другим потокам шанса выполнить свою работу. В этом примере функция greedyWorker постоянно захватывает мьютекс, блокируя доступ к sharedResource для других горутин (функций starvedWorker). Это приводит к тому, что starvedWorker не получает шанса выполнить свою работу.
package main

import (
"fmt"
"sync"
"time"
)

var mu sync.Mutex
var sharedResource int

func greedyWorker() {
for {
mu.Lock()
sharedResource++
fmt.Println("Greedy worker incremented sharedResource to:", sharedResource)
mu.Unlock()
time.Sleep(100 * time.Millisecond) // Короткая задержка для демонстрации
}
}

func starvedWorker(id int, wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
fmt.Printf("Starved worker %d is running\n", id)
sharedResource++
mu.Unlock()
}

func main() {
var wg sync.WaitGroup
wg.Add(5)

go greedyWorker()

for i := 1; i <= 5; i++ {
go starvedWorker(i, &wg)
}

wg.Wait()
fmt.Println("Final sharedResource value:", sharedResource)
}


🚩Способы предотвращения голодания

🟠Справедливые блокировки
Используйте справедливые алгоритмы блокировки, которые предоставляют доступ к ресурсу в порядке очереди.
🟠Управление приоритетами
Обеспечьте сбалансированное управление приоритетами потоков, чтобы потоки с низким приоритетом также получали доступ к ресурсам.
🟠Использование других механизмов синхронизации
Например, вместо использования мьютексов, можно использовать каналы в Go для координации работы между горутинами.
🟠Разделение времени доступа:
Ограничьте время, в течение которого поток может удерживать мьютекс, чтобы другие потоки также могли получить доступ.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Насколько увеличивается слайс при append?

При добавлении элемента слайс увеличивается в два раза, если он достигает текущей ёмкости. Это позволяет минимизировать количество перераспределений памяти. Однако если ёмкости достаточно, увеличение не происходит.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие используешь мьютексы для того, чтобы обезопасить МАПу?

Для обеспечения потокобезопасности мапы в Go обычно используется комбинация мьютексов (sync.Mutex) или RW-мьютексов (sync.RWMutex). Давайте рассмотрим, как каждый из них применяется для защиты мапы.

🚩Использование `sync.Mutex`

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

import (
"fmt"
"sync"
)

type SafeMap struct {
mu sync.Mutex
m map[string]int
}

func NewSafeMap() *SafeMap {
return &SafeMap{
m: make(map[string]int),
}
}

func (sm *SafeMap) Store(key string, value int) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.m[key] = value
}

func (sm *SafeMap) Load(key string) (int, bool) {
sm.mu.Lock()
defer sm.mu.Unlock()
value, ok := sm.m[key]
return value, ok
}

func (sm *SafeMap) Delete(key string) {
sm.mu.Lock()
defer sm.mu.Unlock()
delete(sm.m, key)
}

func main() {
sm := NewSafeMap()
sm.Store("foo", 1)
value, ok := sm.Load("foo")
if ok {
fmt.Println("Found:", value)
} else {
fmt.Println("Not found")
}
}


🚩Использование `sync.RWMutex`

sync.RWMutex предоставляет два типа блокировок: для чтения (RLock) и для записи (Lock). Он позволяет нескольким горутинам одновременно читать данные, если нет активных блокировок для записи. Это улучшает производительность в сценариях с частыми операциями чтения и редкими операциями записи.
package main

import (
"fmt"
"sync"
)

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

func NewSafeMap() *SafeMap {
return &SafeMap{
m: make(map[string]int),
}
}

func (sm *SafeMap) Store(key string, value int) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.m[key] = value
}

func (sm *SafeMap) Load(key string) (int, bool) {
sm.mu.RLock()
defer sm.mu.RUnlock()
value, ok := sm.m[key]
return value, ok
}

func (sm *SafeMap) Delete(key string) {
sm.mu.Lock()
defer sm.mu.Unlock()
delete(sm.m, key)
}

func main() {
sm := NewSafeMap()
sm.Store("foo", 1)
value, ok := sm.Load("foo")
if ok {
fmt.Println("Found:", value)
} else {
fmt.Println("Not found")
}
}


🚩Когда использовать `sync.Mutex` и `sync.RWMutex`

🟠`sync.Mutex`:
Используйте, если операции чтения и записи происходят с примерно одинаковой частотой. Подходит для простых сценариев, где производительность не критична.
🟠`sync.RWMutex`:
Используйте, если чтения происходят значительно чаще, чем записи. Улучшает производительность за счет одновременного выполнения нескольких операций чтения.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие основные структуры данных есть в Go?

Основные структуры данных в Go включают массивы, слайсы, карты (map) и структуры (struct). Они обеспечивают базовую функциональность для работы с данными. Также доступны каналы для синхронизации между потоками.

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM