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

В Go существует два типа каналов: буферизованные (buffered) и небуферизованные (unbuffered). Небуферизованные каналы требуют, чтобы отправляющая и принимающая горутина синхронизировались друг с другом, что делает их блокирующими. Буферизованные каналы имеют определённый размер буфера, и горутина может отправить сообщение в канал, не ожидая немедленного получения, пока буфер не заполнится. Оба типа каналов используются для передачи данных между горутинами и синхронизации их работы.

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

Сегодня — последний день краудфандинга.
Через несколько часов всё закроется, и больше невозможно будет поучаствовать.

Если ты хотел, но откладывал — СЕЙЧАС самое время. Займёт 2 минуты, но изменит твой подход к собеседованиям надолго.

Поддержи easyoffer 2.0 и получи:

🚀 PRO подписка к easyoffer 2.0 на 1 год по цене месячной подписки. Активировать подписку можно в любой момент, например, когда начнешь искать работу. Приглашение на закрытое бета-тестирование

PRO подписка к easyoffer 2.0:

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

Доступ к лучшим ответам на вопросы

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

Доступ к лучшим ответам на задачи

Список тестовых заданий компаний + лучшее решение

Доступ к тренажеру "Проработка вопросов", который позволит очень быстро подготовиться к самым частым вопросам

Доступ к тренажеру "Реальное собеседование", который позволит тренироваться проходить собеседование в конкретную компанию

До конца кампании — остались часы.
Поддержать: https://planeta.ru/campaigns/easyoffer

📌 Если не получается оплатить через карту РФ — напишите мне @kivaiko, и мы найдём удобный способ
🤔 Что такое mutex, какие они бывают и как их использовать?

Mutex (от Mutual Exclusion — взаимное исключение) — это примитив синхронизации, который используется для защиты критических секций ресурса или данных, доступ к которым должен быть ограничен таким образом, чтобы только один поток или горутина могли работать с ним в каждый конкретный момент времени. Mutex гарантирует, что только один поток может входить в защищённую секцию кода, выполняющую операции над общими данными.

🚩Какие бывают Mutex

🟠Блокирующие Mutex (Blocking Mutexes)
Это наиболее распространённый тип мьютексов. При попытке захватить мьютекс, если он уже занят другим потоком, поток блокируется и ожидает, пока мьютекс не будет освобождён.

🟠Неблокирующие Mutex (Non-blocking or Spinlocks)
Вместо блокирования потока, потоки активно проверяют состояние мьютекса в цикле. Это может быть эффективно, если мьютекс захватывается на очень короткое время, так как избегается затрата времени на блокировку и разблокировку потока.

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

Стандартная библиотека предоставляет мьютекс в пакете sync. Вот пример того, как можно использовать мьютекс для синхронизации доступа к общему ресурсу:
package main

import (
"fmt"
"sync"
)

var (
balance int
mutex sync.Mutex
)

func deposit(value int, wg *sync.WaitGroup) {
mutex.Lock() // Захват мьютекса перед изменением переменной balance
fmt.Printf("Depositing %d to account with balance: %d\n", value, balance)
balance += value
mutex.Unlock() // Освобождение мьютекса после изменения
wg.Done()
}

func main() {
var wg sync.WaitGroup
wg.Add(2)
go deposit(100, &wg)
go deposit(200, &wg)
wg.Wait()
fmt.Printf("New Balance %d\n", balance)
}


🚩Рекомендации по использованию

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

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

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

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

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from easyoffer
Такого больше не будет!

Всего пара часов и больше не будет возможности получить:

🚀 PRO подписку к easyoffer 2.0 на 1 год по цене месячной подписки. Активировать подписку можно в любой момент, например, когда начнешь искать работу. Приглашение на закрытое бета-тестирование

👉 Поддержать: https://planeta.ru/campaigns/easyoffer
🤔 Как передаются параметры в функцию — по указателю или по значению?

По умолчанию в Go все параметры передаются по значению, т.е. копируются.
Но если передать указатель на переменную, можно изменить её содержимое. Это единственный способ передать значение по ссылке.
Тип *T указывает, что это указатель, а не значение.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from easyoffer
Финальный отсчёт:
3 часа до конца краудфандинга easyoffer 2.0!


Это не просто скидка. Это шанс поддержать проект, который поможет и вам и тысячам айтишников готовиться к собеседованиям быстрее, эффективнее и увереннее.

За последние недели:
💥 Нас поддержали уже больше 1450 человек;
🔥 Вместе собрали больше 4,5 млн. рублей на запуск проекта;

Но сейчас важнее другое.

Через 3 часа всё закончится.
– Больше не будет подписки за 3 200 руб. на целый год!
– Не будет шанса первыми воспользоваться EasyOffer 2.0 на бета-тестировании

Если вы:

+ Планируете менять работу в этом или следующем году;
+ Хотите иметь под рукой 40,000+ вопросов собеседований с разборами, видео-ответами и тренажёрами;
+ Хотите зафиксировать лучшую цену на целый год… (потом будет в 12 раз дороже)

👉 Тогда просто переходите и поддержите нас сейчас:
https://planeta.ru/campaigns/easyoffer

📢 Три часа — и всё.
Не откладывайте на потом.

Спасибо всем, кто уже с нами! 💙
Forwarded from easyoffer
🚨 60 минут до финала

Через час мы закроем краудфандинг easyoffer 2.0
Это последний шанс вписаться в самые выгодные условия.

👉 https://planeta.ru/campaigns/easyoffer
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from Идущий к IT
Я смотрю на эту цифру и до сих пор не верю.

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

Я ставил планку в 300т рублей. В самом позитивном сценарии 1млн. Но про 5 миллионов… даже мысли не было. Уже в первые часы стало понятно, что кампания идет не по плану. Сайт краудфандинга не выдержал нашей нагрузки и лег 😁

Особенно в последние три дня — просто какой-то разрыв! Я ощущал, как будто ловлю попутный ветер. В последний час не хватало 50к до 5 млн, и я уже думал сам их докинуть, чтобы красиво закрыть 😁

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

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

В такие моменты вспоминаю, с чего всё начиналось. Как 2 года назад я писал свои первые посты на 500 человек о том, как учу программирование. Как записывал первое видео на YouTube про поиск работы. Как пилил первую версию easyoffer, вообще без понимания, что из этого выйдет.

И сейчас я думаю — может, эта история вдохновит кого-то из вас. Может, кто-то запустит свой айтишный проект, найдёт поддержку и соберёт бабки на развитие. Было бы круто

Спасибо за невероятную и колосальную поддержку ❤️
О такой аудитории как вы я не мог мечтать
🤔 Можно ли сделать int(string) и string(int) соответственно?

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

🚩Преобразование строки в целое число

Для этого используется функция strconv.Atoi. Она возвращает два значения: само число и ошибку, если преобразование не удалось.
package main

import (
"fmt"
"strconv"
)

func main() {
str := "123"
num, err := strconv.Atoi(str)
if err != nil {
fmt.Println("Error converting string to int:", err)
} else {
fmt.Println("Converted number:", num)
}
}


🚩Преобразование целого числа в строку

Для этого используется функция strconv.Itoa.
package main

import (
"fmt"
"strconv"
)

func main() {
num := 123
str := strconv.Itoa(num)
fmt.Println("Converted string:", str)
}


🚩Обработка ошибок при преобразовании

Важно обрабатывать ошибки при преобразовании типов, особенно при преобразовании строки в целое число, чтобы избежать неожиданных сбоев в программе.
package main

import (
"fmt"
"strconv"
)

func main() {
str := "abc"
num, err := strconv.Atoi(str)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Converted number:", num)
}
}


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

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
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
🤔 Что известно про паттерн Factory (Фабрика)?

Factory — это паттерн, скрывающий логику создания объектов за единым интерфейсом. Он позволяет:
- инкапсулировать создание объектов разного типа;
- выбирать реализацию в зависимости от условий;
- отделить клиентский код от конкретных типов.
Пример: создание разных видов подключений (TCP, HTTP, WebSocket) через одну фабрику, возвращающую интерфейс Connection.


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

В Go каналы (chan) можно закрывать с помощью close(channel), чтобы показать, что больше не будет отправляться данных.

Как закрыть канал?
ch := make(chan int)
close(ch) // Закрываем канал


Полный пример
package main

import "fmt"

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

go func() {
for i := 1; i <= 3; i++ {
ch <- i // Отправляем данные
}
close(ch) // Закрываем канал
}()

for val := range ch { // Читаем пока канал не закроется
fmt.Println(val)
}

fmt.Println("Канал закрыт, чтение завершено")
}


Выход
1
2
3
Канал закрыт, чтение завершено


🚩Как проверить, закрыт ли канал?

Используем val, ok := <-ch:
- ok == true → канал открыт, есть данные.
- ok == false → канал закрыт.
package main

import "fmt"

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

val, ok := <-ch
fmt.Println("val:", val, "ok:", ok) // val: 0 ok: false
}


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

Пустая структура в Go (struct{}) занимает 0 байт.
Она не содержит ни одного поля, и компилятор оптимизирует её до нуля. Это удобно, например, для использования в map[T]struct{} — в качестве множества без нагрузки на память.


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

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

🟠Управление временем выполнения операций
Контексты позволяют задавать дедлайны и таймауты для операций.
context.WithTimeout создает контекст с таймаутом, после которого операция будет автоматически отменена.
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
defer cancel()


context.WithDeadline создает контекст с дедлайном, который определяет точное время, после которого операция будет отменена.
deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(parentCtx, deadline)
defer cancel()


🟠Явная отмена операций
Контексты позволяют явно отменять операции, что полезно для управления горутинами и предотвращения утечек памяти.
context.WithCancel создает контекст, который может быть отменен вручную с помощью функции cancel.
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()


🟠Передача метаданных
Контексты позволяют передавать данные между функциями, что полезно для передачи информации о запросах, пользователей и других данных.
context.WithValue создает контекст, который содержит пару ключ-значение для передачи метаданных.
type key string
ctx := context.WithValue(parentCtx, key("userID"), 12345)


🟠Вложенные контексты
Можно создавать иерархии контекстов, где каждый дочерний контекст наследует отмену и дедлайны от родительского.
Создание вложенных контекстов позволяет строить гибкие и сложные системы управления временем выполнения и отмены.
ctx1, cancel1 := context.WithCancel(parentCtx)
ctx2, cancel2 := context.WithTimeout(ctx1, 1*time.Second)
defer cancel1()
defer cancel2()


🟠Синхронизация горутин
Контексты обеспечивают механизм для синхронизации и координации работы нескольких горутин.
Пример синхронизации горутин
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()

wg.Add(2)
go func() {
defer wg.Done()
worker(ctx, "Worker 1")
}()
go func() {
defer wg.Done()
worker(ctx, "Worker 2")
}()
wg.Wait()

func worker(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "stopped")
return
default:
fmt.Println(name, "working")
time.Sleep(1 * time.Second)
}
}
}


🟠Управление жизненным циклом запросов
Контексты широко используются в веб-серверах для управления жизненным циклом HTTP-запросов, обеспечивая таймауты и отмену при завершении обработки запросов.
Пример использования контекста в обработчике HTTP-запроса
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-time.After(5 * time.Second):
fmt.Fprintln(w, "Request processed")
case <-ctx.Done():
err := ctx.Err()
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)


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

В реальных проектах часто используются:
- Builder — для конфигурации сложных объектов (например, HTTP-клиентов).
- Factory — для создания сервисов по интерфейсу.
- Singleton — для централизованного логгера или глобальных параметров.
- Также могут применяться Strategy, Adapter, Observer, особенно при построении архитектуры с плагинами, хранилищами, UI-обработкой.


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

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

🚩Основные типы

🟠Статический NAT (Static NAT):
Фиксированный сопоставление: Один внутренний IP-адрес сопоставляется с одним внешним IP-адресом. Используется, когда необходимо, чтобы устройство в локальной сети всегда было доступно под одним и тем же публичным IP-адресом. Например Веб-сервер, который должен быть доступен из интернета под фиксированным IP-адресом.

🟠Динамический NAT (Dynamic NAT):
Внутренние IP-адреса сопоставляются с пулом внешних IP-адресов. Когда внутреннее устройство инициирует соединение с интернетом, ему временно присваивается один из доступных внешних IP-адресов. Например, локальная сеть с большим количеством устройств, где не требуется фиксированный внешний IP-адрес для каждого устройства.

🟠Преобразование адресов и портов (PAT, Port Address Translation), также известный как Маскарадинг (Masquerading):
Несколько внутренних IP-адресов могут использовать один внешний IP-адрес, но различаются по номерам портов. Каждый внутренний IP-адрес и порт сопоставляется с уникальным внешним портом. Например, Домашние или офисные сети, где множество устройств выходят в интернет через один публичный IP-адрес.

🚩Зачем нужен

🟠Сохранение IP-адресов:
IPv4-адресов недостаточно для всех устройств, и NAT позволяет использовать один публичный IP-адрес для множества устройств.

🟠Безопасность:
Внутренние IP-адреса не видны извне, что усложняет потенциальным злоумышленникам попытки атак на внутренние устройства.

🟠Управление подключениями:
NAT позволяет администрировать и контролировать сетевой трафик, предоставляя возможности для управления доступом и приоритизацией трафика.

🚩Принцип работы

🟠Инициирование соединения:
Когда устройство в локальной сети (например, компьютер с IP-адресом 192.168.1.10) инициирует соединение с устройством в интернете, NAT изменяет исходящий IP-адрес и порт на внешний IP-адрес маршрутизатора и уникальный номер порта.

🟠Ответный трафик:
Когда ответный пакет возвращается, NAT использует таблицу сопоставлений, чтобы определить, к какому внутреннему устройству направить пакет, и изменяет внешний IP-адрес и порт обратно на внутренний IP-адрес и порт.

🚩Пример работы PAT

1⃣Внутренний компьютер с IP-адресом 192.168.1.10 и портом 12345 инициирует соединение.
2⃣NAT изменяет это на внешний IP-адрес 203.0.113.1 и порт 54321.
3⃣Ответный пакет от сервера приходит на IP-адрес 203.0.113.1 и порт 54321.
4⃣NAT преобразует это обратно в 192.168.1.10:12345 и отправляет пакет внутреннему компьютеру.

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

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

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

В Go строки представлены как набор байтов ([]byte), но количество символов может отличаться, так как некоторые символы занимают несколько байтов (например, Unicode).

🚩Способы подсчёта символов в строке

len(s) – количество байтов (НЕ символов!)
s := "Привет"
fmt.Println(len(s)) // 12, потому что кириллические символы занимают 2 байта

utf8.RuneCountInString(s) – количество символов (РЕКОМЕНДУЕТСЯ )
package main

import (
"fmt"
"unicode/utf8"
)

func main() {
s := "Привет"
fmt.Println(utf8.RuneCountInString(s)) // 6, правильно считает символы
}


Подсчёт через range (альтернативный метод)
count := 0
for range "Привет" {
count++
}
fmt.Println(count) // 6


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

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


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

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

🚩Задача

В моём проекте был сервис, который обрабатывал тысячи файлов параллельно. Если запускать новую горутину на каждый файл, то память быстро заканчивалась, и сервер "умирал".
Решение: вместо создания тысяч горутин я использовал Worker Pool с фиксированным числом воркеров (например, 5), которые брали задачи из очереди.

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

Есть канал задач (jobs).
Есть пул воркеров (workers), которые читают задачи из канала.
Каждый воркер обрабатывает одну задачу и берёт следующую.
package main

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

// Количество воркеров
const workerCount = 5

// Воркер, который берет задачи из канала и обрабатывает их
func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second) // Имитация обработки
}
}

func main() {
jobs := make(chan int, 10)
var wg sync.WaitGroup

// Запускаем воркеров
for i := 1; i <= workerCount; i++ {
wg.Add(1)
go worker(i, jobs, &wg)
}

// Отправляем задачи в канал
for j := 1; j <= 10; j++ {
jobs <- j
}
close(jobs) // Закрываем канал, чтобы воркеры знали, что задачи кончились

wg.Wait() // Ждём завершения всех воркеров
fmt.Println("Все задачи обработаны")
}


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