Golang | Вопросы собесов
4.33K subscribers
26 photos
694 links
Download Telegram
🤔 Как наследование осуществлено в Golang?


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

🚩Композиция

Позволяет включать одну структуру в другую, что дает возможность использовать методы встроенной структуры. Это часто называют "встраиванием" или "композицией" вместо наследования.
package main

import "fmt"

// Определение структуры
type Engine struct {
Power int
}

func (e Engine) Start() {
fmt.Println("Engine started with power:", e.Power)
}

// Определение другой структуры, которая включает первую
type Car struct {
Brand string
Engine
}

func main() {
myCar := Car{
Brand: "Toyota",
Engine: Engine{Power: 150},
}

fmt.Println("Car brand:", myCar.Brand)
myCar.Start() // Вызов метода встроенной структуры
}


🚩Интерфейсы

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

import "fmt"

// Определение интерфейса
type Drivable interface {
Drive()
}

// Определение структуры, реализующей интерфейс
type Car struct {
Brand string
}

func (c Car) Drive() {
fmt.Println(c.Brand, "is driving")
}

// Еще одна структура, реализующая интерфейс
type Bike struct {
Brand string
}

func (b Bike) Drive() {
fmt.Println(b.Brand, "is driving")
}

// Функция, принимающая интерфейс
func StartDriving(d Drivable) {
d.Drive()
}

func main() {
car := Car{Brand: "Toyota"}
bike := Bike{Brand: "Yamaha"}

StartDriving(car)
StartDriving(bike)
}


🚩Встраивание интерфейсов

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

import "fmt"

// Определение базового интерфейса
type Printer interface {
Print()
}

// Определение другого интерфейса, включающего первый
type AdvancedPrinter interface {
Printer
Scan()
}

// Реализация структуры, реализующей расширенный интерфейс
type MultiFunctionPrinter struct{}

func (m MultiFunctionPrinter) Print() {
fmt.Println("Printing...")
}

func (m MultiFunctionPrinter) Scan() {
fmt.Println("Scanning...")
}

func main() {
mfp := MultiFunctionPrinter{}
mfp.Print()
mfp.Scan()
}


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

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


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

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

🚩Основные компоненты Go `runtime`

Горутины (goroutines) — лёгкие потоки, управляемые Go.
Планировщик (scheduler) — распределяет горутины по потокам ОС.
Модель памяти — управление стеком и кучей.
Сборщик мусора (GC) — автоматически очищает неиспользуемую память.
Syscalls — взаимодействие с ОС.

🚩Горутины и планировщик (scheduler)

Горутины легче, чем потоки ОС: Go может запускать миллионы горутин, а планировщик (M:N) распределяет их по потокам ОС.
Go использует M:N-модель, где:
- M — системные потоки (OS threads).
- N — горутины (goroutines).
- P — процессоры (logical processors) — управляют выполнением горутин.
P привязан к M, а G (горутины) распределяются между `P` динамически.
Это позволяет Go эффективно использовать CPU без ручного управления потоками.
Goroutine (G1, G2, G3, ...) 

Scheduler → P (Processor) → M (OS Thread) → CPU


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

Стек (stack) в Go динамический — он начинается с 2 KB и увеличивается при необходимости.
Если стек заполнен, Go автоматически расширяет его.
В отличие от C, Go не требует malloc/free, так как есть GC (garbage collector).

🚩Сборщик мусора (Garbage Collector)

Go использует автоматический сборщик мусора, который:
Работает параллельно с программой
Минимизирует паузы (low-latency GC)
Очищает неиспользуемые объекты из кучи
import "runtime"

runtime.GC() // Принудительный запуск сборщика мусора (обычно не нужно)


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Внутреннее устройство интерфейса?

Интерфейс состоит из двух частей: указателя на динамический тип и указателя на данные, связанные с этим типом. Такая структура позволяет интерфейсу хранить и управлять значениями разных типов динамически.


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

Составной индекс (Composite Index) — это индекс, который создаётся сразу на нескольких колонках таблицы базы данных. Он помогает ускорить поиск и сортировку данных, особенно если запросы используют условия по нескольким полям.

🚩Зачем нужен составной индекс?

🟠Оптимизация запросов
Если часто выполняются запросы с фильтрацией по нескольким колонкам (WHERE col1 = X AND col2 = Y), составной индекс ускоряет их выполнение.
🟠Ускорение сортировки
Если запросы используют ORDER BY col1, col2, индекс помогает избежать дополнительной сортировки.
🟠Снижение нагрузки на сервер
Без индекса СУБД вынуждена выполнять полный перебор (Full Table Scan), что занимает больше времени.

🚩Как работает составной индекс?

Он строится по нескольким колонкам и фактически представляет собой отсортированную структуру данных. Например, в PostgreSQL или MySQL создаётся так:
CREATE INDEX idx_name ON users (last_name, first_name);


Теперь база данных будет использовать индекс для запросов:
SELECT * FROM users WHERE last_name = 'Иванов' AND first_name = 'Петр';


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

В Go мапа (map[K]V) — это указатель на внутреннюю хеш-структуру. Её "вес" при передаче — 8 байт (64-битная архитектура), так как это просто ссылка.
Но фактический размер в памяти зависит от количества элементов и структуры хеш-таблицы, которая может занимать значительно больше.


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

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

🚩Неизменяемость

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

🚩Структура строки

Внутренне строка представлена структурой, которая содержит два поля:
Указатель на массив байтов: Это указатель на первый элемент массива байт, который фактически хранит символы строки в кодировке UTF-8.
Длина: Количество байт в строке, а не количество рун или символов. Это важное различие, поскольку в UTF-8 один символ может занимать от 1 до 4 байт.

🚩UTF-8 как стандартная кодировка

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

🚩Срезы строк

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

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

Благодаря неизменяемости и способу хранения строк в виде срезов байтов, Go обеспечивает эффективное управление памятью и производительность при работе со строками. Однако необходимо быть осторожным с операциями, которые могут казаться невинными, но приводят к частому созданию новых строк, так как это может повлечь за собой издержки на выделение памяти и сборку мусора.
s := "Hello, world"      // Создание строки
t := s[7:] // Срез строки, создает новую строку "world"

fmt.Println(s) // Выводит: Hello, world
fmt.Println(t) // Выводит: world


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

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
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, используемые?

Поддерживает HTTP/1.0, HTTP/1.1, HTTP/2 и HTTP/3. Использование HTTP/2 встроено в стандартную библиотеку, а поддержка HTTP/3 возможна через сторонние библиотеки.

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

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

🟠Примитивные типы
числа, строки, булевы значения и т.д.
🟠Композитные типы
массивы, срезы, карты, структуры.
🟠Функции
функции различных типов.
🟠Другие интерфейсы
значения, которые реализуют другие интерфейсы.

🚩Примеры

Примитивные типы
package main

import "fmt"

func main() {
var i interface{}
i = 42
fmt.Println(i) // Output: 42

i = "hello"
fmt.Println(i) // Output: hello

i = true
fmt.Println(i) // Output: true
}


Композитные типы
package main

import "fmt"

func main() {
var i interface{}

i = []int{1, 2, 3}
fmt.Println(i) // Output: [1 2 3]

i = map[string]int{"one": 1, "two": 2}
fmt.Println(i) // Output: map[one:1 two:2]

type Person struct {
Name string
Age int
}

i = Person{Name: "Alice", Age: 30}
fmt.Println(i) // Output: {Alice 30}
}


Функции
package main

import "fmt"

func main() {
var i interface{}

i = func() {
fmt.Println("Hello from function")
}

if f, ok := i.(func()); ok {
f() // Output: Hello from function
}
}


Другие интерфейсы
package main

import "fmt"

type Stringer interface {
String() string
}

type Person struct {
Name string
Age int
}

func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}

func main() {
var i interface{}

i = Person{Name: "Alice", Age: 30}

if str, ok := i.(Stringer); ok {
fmt.Println(str.String()) // Output: Alice (30 years old)
}
}


🚩Проверка и приведение типа

Утверждение типа
package main

import "fmt"

func main() {
var i interface{} = 42

if v, ok := i.(int); ok {
fmt.Println("Integer:", v) // Output: Integer: 42
} else {
fmt.Println("Not an integer")
}
}


Использование switch для проверки типа
package main

import "fmt"

func printType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Println("String:", v)
case int:
fmt.Println("Integer:", v)
case bool:
fmt.Println("Boolean:", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}

func main() {
printType("hello") // Output: String: hello
printType(42) // Output: Integer: 42
printType(true) // Output: Boolean: true
printType(3.14) // Output: Unknown type: float64
}


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

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


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

Объектно-ориентированная модель отличается от традиционных ООП-языков, таких как C# или Java. Нет классов и наследования в привычном понимании. Вместо этого используются структуры (structs) и интерфейсы для реализации основных принципов ООП: инкапсуляции, композиции и полиморфизма.

🚩Концепции

🟠Структуры (Structs)
Служат аналогом классов. Они позволяют объединять данные в логически связанные группы.
type Person struct {
Name string
Age int
}


🟠Методы
Могут быть определены для структур, что позволяет связывать функции с типами.
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}


🟠Инкапсуляция
Достигается через модификаторы доступа на уровне пакета. Поля и методы, начинающиеся с заглавной буквы, экспортируемые (public), остальные — нет (private).
type Person struct {
name string // неэкспортируемое поле
Age int // экспортируемое поле
}


🟠Композиция
Go не поддерживает классическое наследование. Вместо этого используется композиция для включения функциональности одного типа в другой.
type Employee struct {
Person
Position string
}


🟠Интерфейсы
Используются для определения поведения. Типы могут реализовывать интерфейсы неявно, просто предоставляя методы, указанные в интерфейсе.
type Greeter interface {
Greet()
}

func SayHello(g Greeter) {
g.Greet()
}

type Person struct {
Name string
}

func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}

func main() {
p := Person{Name: "John"}
SayHello(p)
}


🟠Полиморфизм
Достигается через интерфейсы. Любой тип, который реализует интерфейс, может быть использован вместо него.
type Greeter interface {
Greet()
}

func SayHello(g Greeter) {
g.Greet()
}

type Robot struct {
ID string
}

func (r Robot) Greet() {
fmt.Printf("Greetings, I am robot %s\n", r.ID)
}

func main() {
p := Person{Name: "John"}
r := Robot{ID: "XJ-9"}

SayHello(p)
SayHello(r)
}


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

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

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

Объектно-ориентированное программирование (ООП) в Go и C# реализовано с использованием различных подходов и парадигм, отражающих философию и дизайн каждого языка.

🚩Go

🟠Классы и объекты
Используются структуры (struct).
type Person struct {
Name string
Age int
}

func (p *Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}


🟠Наследование
Используется композиция вместо наследования.
type Employee struct {
Person
Position string
}


🟠Инкапсуляция
Модификаторы доступа на уровне пакета (экспортируемые и неэкспортируемые поля).
type Person struct {
name string // неэкспортируемое поле
Age int // экспортируемое поле
}


🟠Полиморфизм
Реализуется через интерфейсы.
type Greeter interface {
Greet()
}

type Person struct {
Name string
}

func (p *Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}


🚩C#

🟠Классы и объекты
Используются классы.
public class Person {
public string Name { get; set; }
public int Age { get; set; }

public void Greet() {
Console.WriteLine($"Hello, my name is {Name}");
}
}


🟠Наследование
Поддерживается классическое наследование.
public class Employee : Person {
public string Position { get; set; }
}


🟠Инкапсуляция
Модификаторы доступа (public, private, protected).
public class Person {
private string name;
public int Age { get; set; }
}


🟠Полиморфизм
Через виртуальные методы и интерфейсы.
public class Person {
public virtual void Greet() {
Console.WriteLine("Hello!");
}
}

public class Employee : Person {
public override void Greet() {
Console.WriteLine("Hello, I am an employee!");
}
}


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

Уровни изоляции определяют, насколько одна транзакция изолирована от других. Они влияют на читаемость данных и защищённость от конфликтов.
Стандартные уровни:
1. Read Uncommitted — минимальная изоляция, возможны "грязные" чтения.
2. Read Committed — читаются только зафиксированные данные.
3. Repeatable Read — одна и та же строка не изменяется другими транзакциями.
4. Serializable — максимальная изоляция, предотвращает все типы конфликтов, но снижает производительность.
Чем выше уровень — тем безопаснее, но медленнее.


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

Да, в SQL возможен JOIN с вложенными запросами. Это позволяет объединять результаты подзапросов с другими таблицами, что полезно, когда нужно предварительно отфильтровать или агрегировать данные перед объединением.

🚩Как это работает?

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

🚩Пример: JOIN с подзапросом

Допустим, у нас есть две таблицы:
- orders (id, user_id, total)
- users (id, name)
Мы хотим получить всех пользователей и их заказы, но только те заказы, где сумма больше 100
SELECT u.name, sub.total
FROM users u
JOIN (
SELECT user_id, total
FROM orders
WHERE total > 100
) AS sub ON u.id = sub.user_id;


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

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

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

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

🚩Процесс

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

🚩Поток (Thread)

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

🚩Основные различия

🟠Изоляция
Процессы изолированы друг от друга, в то время как потоки делят состояние и ресурсы внутри одного процесса.

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

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

🟠Взаимодействие
Взаимодействие между процессами требует использования межпроцессного взаимодействия (IPC), такого как сокеты, разделяемая память, очереди сообщений и т. д. Потоки внутри процесса могут общаться друг с другом напрямую через общую память.

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

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

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

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

Нельзя напрямую взять ссылку на значение, хранящееся по ключу в карте (map), из-за особенностей реализации карт и управления памятью. Рассмотрим подробнее, почему это так.

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

🟠Внутреннее устройство карты (map)
Карты реализованы на основе хеш-таблиц. Внутреннее устройство карты предполагает, что значения могут перемещаться в памяти при выполнении операций, таких как добавление или удаление элементов. Хеш-таблица может перераспределять (реорганизовывать) свои внутренние структуры для оптимизации доступа к элементам. Это может происходить, например, когда карта увеличивается в размере.

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

🚩Демонстрация проблемы

Здесь попытка взять ссылку на значение из карты могла бы привести к проблемам
package main

import "fmt"

func main() {
m := map[string]int{"a": 1, "b": 2}

// Нельзя делать так:
// p := &m["a"]

// Вместо этого можно работать с копией значения
value := m["a"]
p := &value

fmt.Println("Value:", *p) // 1

// Изменение карты
m["c"] = 3

// Ссылка на значение в карте могла бы стать недействительной
// fmt.Println("Value after map change:", *p)
}


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

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

🟠Работа с копией значения
Получить значение из карты и сохранить его в переменную.
value := m["a"]   


🟠Изменение значения в карте
Если необходимо изменить значение в карте, его нужно сначала извлечь, изменить, а затем снова записать в карту.
value := m["a"]
value = value + 10
m["a"] = value


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

import "fmt"

func main() {
m := map[string]*int{"a": new(int), "b": new(int)}
*m["a"] = 1
*m["b"] = 2

// Теперь можно брать указатели на значения
p := m["a"]
fmt.Println("Value:", *p) // 1

// Изменение значения через указатель
*p = 42
fmt.Println("Updated Value:", *m["a"]) // 42
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM