Они способствуют созданию кода, который является более надежным, поддерживаемым и расширяемым.
Написание тестов помогает обнаружить ошибки и дефекты на ранних стадиях разработки. Регулярное тестирование кода обеспечивает его корректное функционирование и уменьшает количество ошибок.
Тесты служат документацией, показывая, как должен работать код. Это облегчает понимание кода новым разработчикам и помогает избежать регрессий при внесении изменений.
Для упрощения тестирования разработчики разбивают код на небольшие, легко тестируемые модули и функции. Это приводит к более чистому и структурированному коду.
Для облегчения тестирования функции и модули должны иметь четко определенные интерфейсы и зависимости. Это приводит к лучшему дизайну API и модулей.
Тесты помогают выявить излишне сложные и трудно тестируемые участки кода, что стимулирует рефакторинг и упрощение кода.
Это методология разработки, при которой тесты пишутся перед написанием функционального кода. Этот подход оказывает дополнительное влияние на организацию кода:
TDD способствует разработке кода малыми итерациями. Каждый цикл включает написание теста, написание кода для его прохождения и рефакторинг. Это улучшает качество кода и позволяет быстро обнаруживать ошибки
Пишутся только те функции, которые необходимы для прохождения тестов, что помогает сосредоточиться на текущих требованиях и избежать излишней функциональности.
TDD стимулирует создание кода с хорошим дизайном, так как разработчики изначально проектируют код таким образом, чтобы он был легко тестируемым.
Тесты, написанные в рамках TDD, служат живой документацией, описывающей, как должен работать код. Это облегчает понимание кода другими разработчиками и упрощает процесс внесения изменений.
package main
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, but got %d", result)
}
}
package main
func Add(a, b int) int {
return a + b
}
В данном случае рефакторинг не требуется, так как код уже является простым и понятным.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Алгоритмы обеспечивают доступ к общим ресурсам в многопоточной среде без использования традиционных блокирующих механизмов, таких как мьютексы. Основная идея заключается в том, что хотя один или несколько потоков могут быть временно приостановлены, по крайней мере один поток всегда будет прогрессировать. Это достигается с помощью атомарных операций, которые гарантируют целостность данных и предотвращают состояния гонки.
Выполняются полностью или не выполняются вообще, без промежуточных состояний. Они обеспечивают безопасность и целостность данных в многопоточной среде.
Compare-And-Swap (CAS)Одна из самых распространенных атомарных операций, которая сравнивает текущее значение переменной с ожидаемым и, если они совпадают, обновляет переменную новым значением.
Fetch-And-Add: Атомарно увеличивает значение переменной и возвращает старое значение.
Lock-free алгоритмы стараются минимизировать критические секции, то есть участки кода, где потоки взаимодействуют с общими данными, чтобы уменьшить вероятность блокировок и состояний гонки.
В lock-free алгоритмах не используются традиционные механизмы блокировки, такие как мьютексы, что исключает вероятность взаимных блокировок (deadlocks).
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type Node struct {
value int
next *Node
}
type Stack struct {
head *Node
}
func (s *Stack) Push(value int) {
newNode := &Node{value: value}
for {
oldHead := s.head
newNode.next = oldHead
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&s.head)), unsafe.Pointer(oldHead), unsafe.Pointer(newNode)) {
break
}
}
}
func (s *Stack) Pop() (int, bool) {
for {
oldHead := s.head
if oldHead == nil {
return 0, false
}
newHead := oldHead.next
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&s.head)), unsafe.Pointer(oldHead), unsafe.Pointer(newHead)) {
return oldHead.value, true
}
}
}
func main() {
stack := &Stack{}
stack.Push(1)
stack.Push(2)
value, ok := stack.Pop()
if ok {
fmt.Println("Popped:", value)
} else {
fmt.Println("Stack is empty")
}
}
Потоки не блокируют друг друга, что исключает проблему deadlock.
Параллельный доступ к данным без блокировок улучшает производительность, особенно на многоядерных системах.
Избегание блокировок снижает накладные расходы, связанные с контекстными переключениями и синхронизацией.
Lock-free алгоритмы сложны в реализации и отладке из-за необходимости работы с атомарными операциями и контроля состояний гонки.
Не все языки и платформы предоставляют полную поддержку атомарных операций.
Частые повторные попытки атомарных операций могут ухудшить масштабируемость в некоторых сценариях.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Этот подход обеспечивает ряд преимуществ, которые способствуют улучшению качества кода, его поддерживаемости и соответствию требованиям.
Написание тестов перед кодом помогает разработчикам четко определить требования к функции или модулю. Тесты выступают в роли спецификации, описывающей, как должен работать код. Это помогает избежать недоразумений и гарантирует, что все требования учтены с самого начала.
TDD заставляет разработчиков сосредоточиться на минимальной функциональности, необходимой для прохождения тестов. Это предотвращает избыточное проектирование и разработку ненужных функций. Каждый тест фокусируется на одном аспекте функциональности, что помогает создавать чистый и целенаправленный код.
Написание тестов перед кодом стимулирует создание модульного и хорошо структурированного кода. Поскольку тесты проверяют небольшие и изолированные части кода, это побуждает разработчиков разделять большие задачи на более мелкие и управляемые модули, что улучшает дизайн и архитектуру системы.
Тесты, написанные перед кодом, обеспечивают немедленную обратную связь. Если код не проходит тест, это означает, что он не соответствует требованиям. Это позволяет разработчикам быстро обнаруживать и исправлять ошибки, повышая уверенность в корректности кода.
Тесты служат живой документацией кода. Они показывают, как должен работать код и какие случаи использования поддерживаются. Это облегчает понимание кода другим разработчикам и упрощает его поддержку в будущем.
Постоянное написание тестов и их прохождение помогает поддерживать высокое качество кода. Тесты выявляют дефекты на ранних стадиях разработки и обеспечивают, что новые изменения не нарушают существующую функциональность.
package main
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, but got %d", result)
}
}
На этом этапе тест не пройдет, так как функция
Add
еще не реализована.package main
func Add(a, b int) int {
return a + b
}
Теперь тест пройдет, так как функция
Add
реализована корректно.При необходимости выполняется рефакторинг кода для улучшения его структуры, при этом тесты продолжают проходить, что гарантирует сохранение функциональности.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Модули сильно зависят друг от друга. Изменения в одном модуле требуют изменений в других.
Модуль выполняет несколько различных задач. Трудно поддерживать и изменять код.
Содержат слишком много логики. Длинные и сложные для понимания.
Один и тот же код повторяется в нескольких местах.
Код не покрыт тестами. Трудно проверять корректность.
Имена не отражают назначение. Трудно понять код.
Логика кода трудна для понимания. Много условных операторов.
Нет комментариев и документации.
Высокое сцепление:
type UserService struct {
userRepository UserRepository
emailService EmailService
}
Низкая связанность:
type UserService struct {
db Database
businessLogic BusinessLogic
notificationService NotificationService
}
Большие функции и классы:
func ProcessData() {
// Много строк кода
}
Дублирование кода:
func CalculateSum(a, b int) int {
return a + b
}
func AddNumbers(x, y int) int {
return x + y
}
Неразборчивые имена:
func DoSomething(x int) int {
return x * 2
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Включает различные методы, позволяющие процессам обмениваться данными и сигналами.
Однонаправленные каналы между родительским и дочерним процессами. Обмен данными между произвольными процессами.
Обмен сообщениями через общую очередь. System V, POSIX message queues.
Совместное использование памяти для обмена данными. POSIX (
shm_open
), System V shared memory.Синхронизация доступа к ресурсам. POSIX (
sem_open
), System V semaphores.Обмен данными через сетевые соединения. Unix domain sockets, TCP/UDP.
Отправка и обработка событий.
kill()
, signal()
в Unix.Обмен данными через файлы или базы данных. Файлы, SQLite.
Каналы (Pipes)
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
fd := make([]int, 2)
if err := syscall.Pipe(fd); err != nil {
fmt.Println("Ошибка создания канала:", err)
os.Exit(1)
}
pid, err := syscall.ForkExec("", nil, &syscall.ProcAttr{Files: []uintptr{uintptr(fd[0]), uintptr(fd[1]), uintptr(os.Stderr.Fd())}})
if err != nil {
fmt.Println("Ошибка создания процесса:", err)
os.Exit(1)
}
if pid == 0 { // Child process
syscall.Close(fd[0])
_, err := syscall.Write(fd[1], []byte("Hello"))
if err != nil {
fmt.Println("Ошибка записи в канал:", err)
}
syscall.Close(fd[1])
} else { // Parent process
buffer := make([]byte, 5)
syscall.Close(fd[1])
_, err := syscall.Read(fd[0], buffer)
if err != nil {
fmt.Println("Ошибка чтения из канала:", err)
}
syscall.Close(fd[0])
fmt.Printf("Получено: %s\n", buffer)
}
}
Общая память (Shared Memory)
package main
import (
"fmt"
"os"
"syscall"
)
func main() {
// Создание сегмента разделяемой памяти
segmentID, _, err := syscall.Syscall(syscall.SYS_SHMGET, uintptr(syscall.IPC_PRIVATE), uintptr(4), uintptr(syscall.S_IRUSR|syscall.S_IWUSR))
if err != 0 {
fmt.Println("Ошибка создания сегмента разделяемой памяти:", err)
os.Exit(1)
}
// Присоединение к сегменту
sharedMemory, _, err := syscall.Syscall(syscall.SYS_SHMAT, segmentID, 0, 0)
if err != 0 {
fmt.Println("Ошибка присоединения к разделяемой пам
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Позволяют синхронизировать доступ к ограниченным ресурсам, в данном случае — к оперативной памяти. Семафор может быть инициализирован значением 1, указывая, что доступен только один слот памяти. Когда один из процессов захватывает семафор, другой процесс будет заблокирован, пока семафор не освободится.
package main
import (
"fmt"
"sync"
"time"
)
var mutex sync.Mutex
func process(id int) {
mutex.Lock() // Захват семафора
fmt.Printf("Process %d: Accessing memory\n", id)
time.Sleep(2 * time.Second) // Имитируем доступ к памяти
fmt.Printf("Process %d: Releasing memory\n", id)
mutex.Unlock() // Освобождение семафора
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
process(1)
}()
go func() {
defer wg.Done()
process(2)
}()
wg.Wait()
}
Обеспечивают эксклюзивный доступ к критической секции. Один процесс захватывает мьютекс перед доступом к памяти, другой процесс будет заблокирован до освобождения мьютекса.
package main
import (
"fmt"
"sync"
"time"
)
var mutex sync.Mutex
func process(id int) {
mutex.Lock() // Захват мьютекса
fmt.Printf("Process %d: Accessing memory\n", id)
time.Sleep(2 * time.Second) // Имитируем доступ к памяти
fmt.Printf("Process %d: Releasing memory\n", id)
mutex.Unlock() // Освобождение мьютекса
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
process(1)
}()
go func() {
defer wg.Done()
process(2)
}()
wg.Wait()
}
Может быть использована для координации доступа к памяти, позволяя процессам обмениваться сообщениями о доступности ресурса.
package main
import (
"fmt"
"time"
)
type Message struct {
processID int
}
func accessMemory(processID int) {
fmt.Printf("Process %d: Accessing memory\n", processID)
time.Sleep(2 * time.Second) // Имитируем доступ к памяти
fmt.Printf("Process %d: Releasing memory\n", processID)
}
func main() {
msgChannel := make(chan Message)
go func() {
// "Дочерний" процесс
message := Message{processID: 1}
msgChannel <- message
msg := <-msgChannel
accessMemory(msg.processID)
}()
go func() {
// "Родительский" процесс
message := Message{processID: 2}
msgChannel <- message
msg := <-msgChannel
accessMemory(msg.processID)
}()
msg := <-msgChannel // Получаем сообщение от дочернего процесса
msgChannel <- msg // Отправляем обратно для завершения доступа
msg = <-msgChannel // Получаем сообщение от родительского процесса
msgChannel <- msg // Отправляем обратно для завершения доступа
time.Sleep(3 * time.Second) // Подождем завершения горутин
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Объектно-ориентированное программирование (ООП) в Go и C# реализовано с использованием различных подходов и парадигм, отражающих философию и дизайн каждого языка.
Используются структуры (
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)
}
Используются классы.
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
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Объектно-ориентированная модель отличается от традиционных ООП-языков, таких как C# или Java. Нет классов и наследования в привычном понимании. Вместо этого используются структуры (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
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Реализуют разные концепции и обладают различными характеристиками. Встраивание используется для композиции и повторного использования кода, а не для создания иерархий классов, как в традиционном наследовании.
Наследование: В языках с традиционным ООП, таких как C# или Java, наследование создает иерархии классов, где подклассы наследуют свойства и методы суперклассов. Подклассы могут переопределять методы суперклассов (полиморфизм) и добавлять новые методы.
Встраивание: Go не поддерживает иерархии классов. Встраивание позволяет включать типы в другие типы без создания строгих иерархий. Встраивание не создает отношений "is-a" (как в наследовании), а реализует отношения "has-a". Здесь
Employee
наследует свойства и методы Person
.public class Person {
public string Name { get; set; }
public void Greet() {
Console.WriteLine($"Hello, my name is {Name}");
}
}
public class Employee : Person {
public string Position { get; set; }
}
Здесь
Employee
включает Person
как встроенное поле, но не наследует его в традиционном смысле.type Person struct {
Name string
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}
type Employee struct {
Person
Position string
}
func main() {
e := Employee{Person: Person{Name: "John"}, Position: "Developer"}
e.Greet() // Вызов метода Greet из встроенного типа Person
fmt.Println("Position:", e.Position)
}
Наследование: Создает жесткую связь между классами, что может привести к хрупким и негибким иерархиям. Подклассы зависят от реализации суперклассов, что может привести к проблемам при изменении суперклассов.
Встраивание: Используется композиция, что способствует более гибкой и модульной архитектуре. Встроенные типы могут быть заменены или изменены без изменения иерархии.
Наследование: Подклассы могут переопределять методы суперклассов, что является основой для полиморфизма.
Встраивание: методы встроенных типов могут быть "переопределены" путем объявления методов с теми же именами в внешнем типе, но это не является полноценным переопределением как в наследовании. Вызов методов не поддерживает виртуальные методы как в C# или Java.
type Person struct {
Name string
}
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s\n", p.Name)
}
type Employee struct {
Person
}
func (e Employee) Greet() {
fmt.Println("Hello, I am an employee")
}
func main() {
e := Employee{Person: Person{Name: "John"}}
e.Greet() // Вызов метода Greet из Employee
e.Person.Greet() // Вызов метода Greet из встроенного типа Person
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Существует несколько способов написания обобщенного кода. До выхода Go 1.18, который представил встроенную поддержку обобщений (generics), разработчики использовали различные техники для достижения аналогичного эффекта.
С выходом Go 1.18 язык получил встроенную поддержку обобщений, позволяющую создавать обобщенные функции и типы. Это позволяет писать код, который работает с любыми типами, определенными параметрами.
package main
import "fmt"
func Map[T any](arr []T, f func(T) T) []T {
result := make([]T, len(arr))
for i, v := range arr {
result[i] = f(v)
}
return result
}
func main() {
nums := []int{1, 2, 3, 4}
doubled := Map(nums, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2, 4, 6, 8]
}
Пример обобщенного типа:
package main
import "fmt"
type Pair[T any, U any] struct {
First T
Second U
}
func main() {
p := Pair[int, string]{First: 1, Second: "one"}
fmt.Println(p) // {1 one}
}
До появления обобщений, интерфейсы были основным способом достижения обобщенности. Интерфейсы позволяют определять функции, которые могут работать с любыми типами, реализующими определенные методы.
package main
import "fmt"
type Stringer interface {
String() string
}
func Print(s Stringer) {
fmt.Println(s.String())
}
type Person struct {
Name string
}
func (p Person) String() string {
return p.Name
}
func main() {
p := Person{Name: "Alice"}
Print(p) // Alice
}
Пустой интерфейс (
interface{}
) может содержать значения любого типа. Это позволяет создавать функции, принимающие значения любых типов, но требует явного приведения типов.package main
import "fmt"
func Print(v interface{}) {
fmt.Println(v)
}
func main() {
Print(42)
Print("Hello")
Print([]int{1, 2, 3})
}
Рефлексия позволяет программам исследовать и изменять структуру и поведение объектов во время выполнения. Это мощный, но сложный способ написания обобщенного кода.
package main
import (
"fmt"
"reflect"
)
func Print(v interface{}) {
rv := reflect.ValueOf(v)
fmt.Println("Type:", rv.Type(), "Value:", rv)
}
func main() {
Print(42) // Type: int Value: 42
Print("Hello") // Type: string Value: Hello
Print([]int{1, 2, 3}) // Type: []int Value: [1 2 3]
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Контексты (
context.Context
) широко используются для передачи метаданных, управления временем выполнения и отмены операций в многопоточной среде. Пакет context
предоставляет несколько видов контекстов, каждый из которых предназначен для различных сценариев использования. context.Background()
возвращает пустой контекст, который обычно используется как корневой контекст в программах. Он не имеет отмены или дедлайна и не содержит значений.ctx := context.Background()
context.TODO()
также возвращает пустой контекст и используется в случаях, когда еще не ясно, какой контекст следует использовать. Это временный заполнитель, который можно заменить на другой контекст позже.ctx := context.TODO()
context.WithCancel(parent Context)
создает дочерний контекст, который может быть отменен явно вызовом функции отмены (cancel
). Это полезно для контроля выполнения горутин.ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
// Работа горутины
<-ctx.Done() // Ожидание отмены контекста
fmt.Println("Goroutine canceled")
}()
// Отмена контекста после некоторого времени
time.Sleep(2 * time.Second)
cancel()
context.WithDeadline(parent Context, d time.Time)
создает дочерний контекст, который будет автоматически отменен по истечении заданного времени (дедлайна).deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
select {
case <-time.After(6 * time.Second):
fmt.Println("Done")
case <-ctx.Done():
fmt.Println("Context canceled:", ctx.Err())
}
context.WithTimeout(parent Context, timeout time.Duration)
создает дочерний контекст, который будет автоматически отменен через заданное время (таймаут). Это упрощенный вариант WithDeadline
.ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
select {
case <-time.After(6 * time.Second):
fmt.Println("Done")
case <-ctx.Done():
fmt.Println("Context canceled:", ctx.Err())
}
context.WithValue(parent Context, key, val interface{})
создает дочерний контекст, который несет значение, связанное с заданным ключом. Это полезно для передачи метаданных между функциями.type key string
func main() {
ctx := context.WithValue(context.Background(), key("userID"), 12345)
process(ctx)
}
func process(ctx context.Context) {
if v := ctx.Value(key("userID")); v != nil {
fmt.Println("UserID:", v)
} else {
fmt.Println("UserID not found")
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM