package
0.0.0-20240609144401-67ebb27ac8b0
Repository: https://github.com/sshlykov/algorithmsandtasks.git
Documentation: pkg.go.dev

# Packages

No description provided by the author
No description provided by the author

# README

Первое занятие: Синтаксис + Синхронизация
TOC
  • Задание 1 (link)
  • Задание 2 (link)
  • Задание 3 (link)
  • Задание 4 (link)
  • Задание 5 (link)
  • Задание 6 (link)
  • Задание 7 (link)
  • Задание 8 (link)
  • Задание 9 (link)
  • Задание 10 (link)
Задание 1
package main

import "fmt"

func main() { s := "hello" s[0] = 'H' fmt.Println(s) }

Что происходит:
S является строкой, в первый байт строки мы пытаемся заменить на литерал руны
Это сработало бы во многих языках, но Golang запрещает это делать т.к. строки не мутабельны

В результате:
Скорее всего компилятор не даст собрать такую программу
Кроме того, тут присутствует не совместимость типов, т.к. по дефолту строка - набор UTF8 байт, а мы пытаемся подставить rune

Как решить:
через strings Buffer
разрезать строку и объединить через пакет fmt

(top)

Задание 2
package main

func main() { ch := make(chan int, 1) for i := 0; i < 10; i++ { select { case x := <-ch: println(x) case ch <- i: } } }

Что происходит:
Создается буферизированный канал из int, далее по циклу вызывается select
Сам select равновероятно обрабатывает 2 ситуации - чтение из канала и запись в него

Что может произойти:
Идеальная ситуация - паттерн запись - чтение, число записано и, в следующей итерации прочитано
Хотя селект не гарантирует последовательность операции, может произойти так, что буфер заполнится; и следующая попытка записать заблокирует поток исполнения и будет ожидать прочтения
На самом деле не так, селект умнее

(top)

Задание 3
package main

import "fmt"

func add(s []string) { s = append(s, "x") }

func main() { s := []string{"a", "b", "c"} r := s[1:2] add(r) fmt.Println(s) }

Что происходит (len/cap):
Инициализируется слайс из строк s(3,3), выбирается фрагмент из слайса r(1,2);
К "подслайсу" применяется функция, которая потенциально должна добавить "x"; После чего мы должны вывести s

Какие проблемы:
добавление в "подслайс" изменит исходный слайс (len < cap), s предается включая ссылку из r

Как исправить:
Смотря что мы хотим сделать... Выглядит как будто мы хотим сделать копию r и обновить ее.
В целом такая идея плоха, сайд-эффекты всегда приводят к сомнительному поведению; лучше сделать так, чтобы функция вернула значение и записала
но если очень хочется - создаем r через copy(); передаем в add ссылку; в функции обновляем значение

(top)

Задание 4
package main

func main() { digits := []int{1, 2, 3, 4, 5} for _, d := range digits { defer println(d) } }

Что происходит:
Берем список из чисел и заполняем defer; должно вывести цифры в обратном порядке;
println из фичей, лучше не использовать;

Какие проблемы:
потенциальная проблема с протечкой данных, т.к. println(d) вызывается внутри цикла (не явная очередь исполнения),
в простом примере очевиден вывод (обратный), но в усложненных задачах можно и пропустить;

Как исправлять:
завернуть в callback; вызвать без defer

(top)

Задание 5
package main

func main() { defer func() { if err := recover(); err != nil { println(err) } }() go func() { panic(123) }() time.Sleep(time.Hour) }

Что происходит:
объявили в defer callback который призван к восстановлению в случае паники в рутине main
запустили отдельную рутину и вызвали панику, замкнули рутину main с помощью sleep
Паника будет запущена в рутине и приложение остановится, так как в ней нет обработки паники

Как исправлять:
перенести обработку паники в рутину, которая паникует\может запаниковать

(top)

Задание 6
package main

import ( "fmt" "runtime" )

func main() { runtime.GOMAXPROCS(1) ch := 0 go func() { ch = 1 }() for ch == 0 { } fmt.Println("finish") }

Что происходит:
ограничили нашу программу 1 физическим потоком; объявили "общую" переменную для main и еще одной рутиной
запустили отдельную рутину, которая изменит переменную; Замкнули рутину main в цикл, который остановится только после изменения переменной
По идее программа не должна дойти до finish, тк. Всего 1 поток. Но по факту, в данный момент у Go вытесняющая многозадачность

Как исправлять:
Не писать так - тут потенциальная гонка; компилятор может не обновить значение
Использовать канал, atomic, mutex, wg

(top)

Задание 7
package main

import "fmt"

func main() { values := []int{} for _, v := range []int{1, 2, 3, 4, 5} { values = append(values, v) } pointers := []*int{} for _, v := range values { pointers = append(pointers, &v) } for _, v := range pointers { fmt.Println(*v) } }

Что происходит:
присваиваем пустой слайс из интов в переменную values, переносим значения из цикла (1..5) внутрь values
присваиваем пустой слайс из ссылок на числовое значение, переносим ссылки на значение итератора внутрь слайса
По циклу печатаем разыменованное из предыдущего слайса значение; Ссылка (на v) во второй итерации одинаковая и мы получим 5-5-5-5-5 по итогу

Как исправлять:
Создавать новую ссылку из values; не использовать v;

(top)

Задание 8
package main

import "fmt"

func main() { a := unsafe.Pointer(&struct{}{}) b := unsafe.Pointer(&struct{}{}) if print { fmt.Println(a) fmt.Println(b) } // Что напечатает этот вызов // 1. Если print == true // 2. Если print == false // 3. Будет ли разница, почему fmt.Println(a == b) }

Что происходит:
Создаем 2 переменные, в которые складываем ссылку на значение инициализированной пустой структуры;
внешняя переменная контролирует запись в лог этих ссылок; проверяется равенство этих ссылок
1) напечатается 2 ссылки на пустую структуру и true; 2) напечатается только true
согласно спецификации одинаковые нулевые значения могут иметь одинаковую ссылку в памяти; с другой стороны не понятно, оптимизирует ли значения компилятор

(top)

Задание 8
package main

import (
	"fmt"
	"math/rand"
	"time"
)


// REVIEW: не стоит использовать магические цифры для предсказуемости поведения
// REVIEW: rand.Intn(1000) - [0..999] приведет к непредсказуемости во время модульного тестирования + CI
// REVIEW: следует обратить внимание, лучше либо вынести в конфиг или в параметр функции
func fetchDataWrong(source string, data chan<- string) {
    // Симуляция ответа запроса разного по продолжительности.
    time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))

    // Симуляция ошибки в 30%.
    // REVIEW: следует использовать rand.Float64 чтобы задавать процент явно
    // REVIEW: 3, 10 - магические числа
    if rand.Intn(10) < 3 {
        // REVIEW: сформировать структуру под ошибку и вынести значение
        data <- fmt.Sprintf("Failed to fetch data from %s", source)
        return
    }

    data <- fmt.Sprintf("Data from %s", source)
}

// REVIEW: Слишком сложная функция; 
// REVIEW: Необходимо разделить функциональность конфигурации, управления go-рутинами и логикой
// REVIEW: Нужно обоснование, почему функция использует один и тот-же канал для успешной обработки и ошибок;
func main() {
    sources := []string{"Source1", "Source2", "Source3", "Source4", "Source5"}

    // REVIEW: нужно обоснование почему используется канал из 100 записей, тогда как всего 5 рутин (используется больше памяти, чем необходимо);
    // REVIEW: Для такой ситуации можно не тратить память и использовать не буферизированный канал.    
    // REVIEW: нет опций чтобы его закрыть; Как обосновывается то, что не будет утечки go-рутин?
    data := make(chan string, 100)

    for _, source := range sources {
        go fetchDataWrong(source, data)
    }

    // REVIEW: Ошибки и успехи пишутся в один канал. Стоит создать структуру передаваемому параметру\ошибке.  
    for i := 0; i < len(sources); i++ {
        fmt.Println(<-data)
    }
}

(top)

Задание 9
package throttle

import (
    "time"
    "sync"
)

// HOF и замыкание
func Throttle(f func(), duration time.Duration) func() {
    var once sync.Once 
    var mu sync.Mutex
    var lastTime time.Time

    return func() {
        mu.Lock()
        defer mu.Unlock()

        once.Do(func() {
            lastTime = time.Now().Add(-duration)
        })

        if time.Since(lastTime) < duration {
            return
        }

        f()

        lastTime = time.Now()
    }
}
(top)

Задание 10
package cache

import "sync"

type item[T any] struct {
	value T
}

type InMemoryCache[Key comparable, Val any] struct {
	mu    sync.RWMutex
	cache map[Key]item[Val]
}

func NewInMemoryCache[Key comparable, Val any]() *InMemoryCache[Key, Val] {
	return &InMemoryCache[Key, Val]{
		cache: make(map[Key]item[Val]),
	}
}

func (c *InMemoryCache[Key, Val]) Put(key Key, value Val) {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.cache[key] = item[Val]{value: value}
}

func (c *InMemoryCache[Key, Val]) Get(key Key) (Val, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	v, ok := c.cache[key]
	if ok {
		return v.value, true
	}

	return v.value, false
}

func (c *InMemoryCache[Key, Val]) GetOrCreate(key Key, value Val) Val {
	if v, ok := c.Get(key); ok {
		return v
	}

	c.Put(key, value)
	return value
}
(top)