Горутины и потоки (треды) в традиционном понимании операционных систем — это две различные концепции параллельного выполнения кода, каждая из которых имеет свои особенности и преимущества. Вот ключевые различия между ними.
Горутины — это легковесные "зеленые" потоки, управляемые Go runtime. Они не являются потоками операционной системы, и Go runtime отвечает за их планирование и выполнение на доступных физических потоках. Это позволяет создавать тысячи и даже миллионы горутин в рамках одного приложения с относительно небольшими затратами памяти и CPU.
Треды — это потоки выполнения, управляемые непосредственно операционной системой. Каждый тред занимает значительно больше ресурсов, чем горутина, особенно в плане памяти и времени на создание и управление. Треды более подходят для задач, требующих высокой вычислительной мощности и прямого взаимодействия с операционной системой.
Горутины потребляют гораздо меньше памяти по сравнению с тредами. Например, стек горутины начинается с нескольких килобайт, что значительно уменьшает затраты при масштабировании.
Треды требуют большего количества памяти для каждого стека, обычно начиная от нескольких сотен килобайт до мегабайтов. Это ограничивает количество потоков, которые могут быть активными одновременно без значительного увеличения затрат на ресурсы.
Горутины могут масштабироваться до большого количества параллельных задач благодаря меньшим требованиям к ресурсам и управлению со стороны runtime Go.
Треды ограничены в масштабируемости физическими ресурсами системы и более высокими затратами на управление.
Горутины имеют намного более эффективный контекст переключения, так как Go runtime оптимизирован для работы с большим количеством горутин и их переключением.
Треды терпят большие затраты времени на переключение контекста, так как операционной системе требуется больше времени для управления потоками.
package main
import (
"fmt"
"time"
)
func say(text string) {
for i := 0; i < 5; i++ {
fmt.Println(text)
time.Sleep(time.Millisecond * 500)
}
}
func main() {
go say("Hello")
say("World")
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Линтеры интегрируются в IDE или CI/CD пайплайны. Например, golangci-lint используется для анализа Go-кода.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Это неизменяемые последовательности байтов, обычно представляющие текст в кодировке UTF-8. Работа с ними в Go основывается на использовании встроенных функций, методов стандартной библиотеки и специальных типов данных.
Самый простой способ объединить строки — использовать оператор
+
или функцию fmt.Sprintf
.package main
import "fmt"
func main() {
s1 := "Привет"
s2 := "Мир"
result := s1 + ", " + s2 + "!"
fmt.Println(result) // Привет, Мир!
// Через fmt.Sprintf
formatted := fmt.Sprintf("%s, %s!", s1, s2)
fmt.Println(formatted) // Привет, Мир!
}
Функция
len
возвращает длину строки в байтах, а не в символах.package main
import "fmt"
func main() {
str := "Привет"
fmt.Println(len(str)) // 12, так как кириллические символы занимают 2 байта
}
Строки можно обойти как в виде байтов, так и в виде рун (Unicode символов).
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Привет"
// Итерация по байтам
for i := 0; i < len(str); i++ {
fmt.Printf("Байт: %x\n", str[i])
}
// Итерация по символам (рунам)
for _, runeValue := range str {
fmt.Printf("Символ: %c\n", runeValue)
}
// Количество рун
fmt.Println("Количество символов:", utf8.RuneCountInString(str))
}
Так как строки неизменяемы, для извлечения подстроки используется срез (
slice
).package main
import "fmt"
func main() {
str := "Привет, Мир!"
substring := str[8:] // С 8-го байта до конца
fmt.Println(substring) // Мир!
}
Функции
strings.Split
и strings.Join
из пакета strings
позволяют разбивать строку на части или объединять части в строку.package main
import (
"fmt"
"strings"
)
func main() {
str := "apple,banana,cherry"
// Разделение строки
parts := strings.Split(str, ",")
fmt.Println(parts) // [apple banana cherry]
// Объединение строк
joined := strings.Join(parts, " | ")
fmt.Println(joined) // apple | banana | cherry
}
Для поиска подстроки можно использовать
strings.Contains
, strings.Index
или strings.HasPrefix/strings.HasSuffix
. Для замены — strings.Replace
.package main
import (
"fmt"
"strings"
)
func main() {
str := "Go — это круто!"
// Поиск подстроки
fmt.Println(strings.Contains(str, "круто")) // true
fmt.Println(strings.Index(str, "это")) // 4
// Замена подстроки
newStr := strings.Replace(str, "круто", "отлично", 1)
fmt.Println(newStr) // Go — это отлично!
}
Стандартная библиотека предоставляет функции для изменения регистра строки:
strings.ToLower
и strings.ToUpper
.package main
import (
"fmt"
"strings"
)
func main() {
str := "Hello, Go!"
fmt.Println(strings.ToUpper(str)) // HELLO, GO!
fmt.Println(strings.ToLower(str)) // hello, go!
}
Удаление пробелов или других символов с начала/конца строки выполняется с помощью
strings.Trim
, strings.TrimSpace
и других функций.package main
import (
"fmt"
"strings"
)
func main() {
str := " Hello, Go! "
fmt.Println(strings.TrimSpace(str)) // "Hello, Go!"
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🤔1
- Документо-ориентированная база данных.
- Хранит данные в формате BSON (похож на JSON).
- Подходит для гибкой схемы или её отсутствия.
- Хороша для быстрого прототипирования и хранения неструктурированных данных.
PostgreSQL:
- Реляционная СУБД с жёсткой схемой.
- Использует таблицы и SQL-запросы.
- Сильная поддержка транзакций, связей, индексов и сложных запросов.
Mongo — для гибкости и масштабируемости, Postgres — для структурированных и строгих данных.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Это интерфейс, который не содержит методов. Поскольку интерфейсы в Go определяют поведение, которое тип должен реализовать, пустой интерфейс, не имеющий методов, автоматически реализуется всеми типами. Это делает пустой интерфейс универсальным контейнером для значений любого типа.
Пустой интерфейс может содержать значение любого типа, потому что все типы в Go автоматически реализуют пустой интерфейс.
Пустой интерфейс широко используется для создания обобщенных (generic) структур данных, функций и методов, которые могут работать с данными любых типов.
Пустой интерфейс может использоваться для хранения значений различных типов в одной переменной.
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 printValue(v interface{}) {
fmt.Println(v)
}
func main() {
printValue(42)
printValue("hello")
printValue(true)
}
Пустой интерфейс используется для создания структур данных, которые могут хранить значения различных типов.
package main
import "fmt"
func main() {
var values []interface{}
values = append(values, 42, "hello", true)
for _, v := range values {
fmt.Println(v)
}
}
Пустой интерфейс используется для обработки данных различных типов, например, при парсинге JSON.
package main
import (
"encoding/json"
"fmt"
)
func main() {
jsonData := `{"name": "Alice", "age": 30}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)
fmt.Println(result)
}
При работе с пустым интерфейсом часто возникает необходимость проверить тип хранимого значения и привести его к конкретному типу. Это можно сделать с помощью утверждения типа (
type assertion
) или конструкции switch
.Утверждение типа позволяет проверить и преобразовать значение пустого интерфейса к конкретному типу.
package main
import "fmt"
func main() {
var i interface{} = "hello"
s, ok := i.(string)
if ok {
fmt.Println("String:", s) // Output: String: hello
} else {
fmt.Println("Not a string")
}
n, ok := i.(int)
if ok {
fmt.Println("Integer:", n)
} 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
👍5🔥1
Кэширование помогает:
- Ускорить доступ к часто используемым данным.
- Снизить нагрузку на сервер или БД.
- Сократить сетевой трафик и задержки.
- Обеспечить плавную работу при временных перебоях внешних сервисов.
Кэш может быть в памяти, на диске, на клиенте, в браузере или на уровне CDN.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
В Go обычный
map
не потокобезопасен. Если несколько горутин одновременно записывают в map
, возникнет ошибка "fatal error: concurrent map writes". Решение: использовать синхронизацию через
sync.Mutex
, sync.RWMutex
или sync.Map
. Блокируем доступ на запись и чтение через
sync.Mutex
. package main
import (
"fmt"
"sync"
)
type SafeMap struct {
mu sync.Mutex
m map[string]int
}
func (s *SafeMap) Set(key string, value int) {
s.mu.Lock() // Блокируем доступ
defer s.mu.Unlock()
s.m[key] = value
}
func (s *SafeMap) Get(key string) int {
s.mu.Lock() // Блокируем на чтение
defer s.mu.Unlock()
return s.m[key]
}
func main() {
safeMap := SafeMap{m: make(map[string]int)}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
safeMap.Set(fmt.Sprintf("key%d", i), i)
}(i)
}
wg.Wait()
fmt.Println("Готово:", safeMap.Get("key5")) // Получаем значение без гонок данных
}
sync.RWMutex
позволяет нескольким горутинам читать одновременно, но блокирует запись. type SafeMap struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeMap) Set(key string, value int) {
s.mu.Lock() // Блокируем только на запись
defer s.mu.Unlock()
s.m[key] = value
}
func (s *SafeMap) Get(key string) int {
s.mu.RLock() // Разрешаем множественное чтение
defer s.mu.RUnlock()
return s.m[key]
}
sync.Map
из стандартной библиотеки уже потокобезопасен, но работает немного медленнее обычного map
из-за внутренних механизмов. package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
m.Store(i, i*10) // Потокобезопасная запись
}(i)
}
wg.Wait()
val, ok := m.Load(5) // Потокобезопасное чтение
if ok {
fmt.Println("Значение:", val)
}
}
Использование канала (`chan`) вместо `map`
Вместо
map
можно передавать данные через канал (chan
), если логика позволяет. package main
import (
"fmt"
)
func main() {
ch := make(chan map[string]int, 1)
ch <- make(map[string]int)
go func() {
m := <-ch
m["key"] = 42
ch <- m
}()
m := <-ch
fmt.Println(m["key"]) // 42
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
1. Оптимизация сложных WHERE условий – ускоряют фильтрацию по нескольким полям.
2. Ускорение сортировки (ORDER BY) – если порядок столбцов в индексе совпадает с сортировкой, MySQL использует индекс.
3. Оптимизация соединений (JOIN) – индексы помогают MySQL быстрее находить соединяемые записи.
4. Снижение нагрузки на БД – индексы уменьшают количество операций чтения с диска.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3