Skip to content

Golang chapter 0190#382

Open
korepanov wants to merge 18 commits intosenjun-team:mainfrom
korepanov:golang-chapter-0190
Open

Golang chapter 0190#382
korepanov wants to merge 18 commits intosenjun-team:mainfrom
korepanov:golang-chapter-0190

Conversation

@korepanov
Copy link
Copy Markdown
Contributor

No description provided.

fmt.Printf("validNumber=%d, invalidNumber=%d, unknownNumber=%d\n",
validNumber, invalidNumber, elementsNumber-validNumber-invalidNumber)
}
func main() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь и далее. Форматирование кода. Нет пробела между функциями.


Метод `Add` должен быть вызван перед запуском горутин, а не внутри них! В противном случае мы не можем быть уверены, что вызвали его до метода `Wait`.

Следующий пример демонстрирует использование `sync.WaitGroup`. Программа должна обработать матрицу оборудования размером *1 млн.* элементов. Каждый элемент представляет собой структуру из имени оборудования и признака того, что оно валидно. Допустимым именем оборудования является любая строка, в которой отсутствуют символы `"!@#$%^&*()"`. В противном случае имя является недопустимым. Необходимо проставить признак допустимости имени для каждого элемента.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

в которой отсутствуют символы "!@#$%^&*()"

Когда хочешь получить валидную строку, то описываешь разрешенные символы, а не запрещенные. Например, разрешены alphanum и пробелы. А в твоем случае получается, что также разрешены непечатаемые символы, управляющие символы, бэктики, знак равно и тд и тп.

@@ -0,0 +1,870 @@
# Глава 19. Управление параллельным исполнением

В этой главе мы познакомимся со счетчиком `sync.WaitGroup`. Он позволяет дождаться окончания работы нескольких горутин. До сих пор мы использовали для этого каналы. Счетчик `sync.WaitGroup` предоставляет более удобные и надежные средства для этих целей.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

и надежные средства

из формулировки складывается впечатление, что каналы работают нестабильно) Ты же имел ввиду надежные в плане недопускания ошибок в коде? Лучше переформулируй


Также отметим один важный момент. Функция `runtime.NumCPU` подсчитывает не только количество физических ядер, но и количество логических. Для некоторых задач нужно знать именно число физических ядер, потому что распараллеливать задачи по логическим не имеет смысла. Иногда это может даже замедлить работу. Например, для тяжелых математических расчетов, имеющих большое количество операций с плавающей точкой.

Такие задачи плохо запускать параллельно на ядрах, которые имеют только один блок для операций с плавающей точкой (FPU). Потоки запущенные, на одном ядре, будут конкурировать за FPU.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Потоки запущенные, на одном ядре, будут конкурировать за FPU.

Потоки, запущенные на одном ядре, будут конкурировать за FPU.

И предлагаю добавить ссылку:

https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D1%81%D0%BE%D0%BF%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80

cpuNumber = 6
```

Понятное дело, что вывод индивидуален, в зависимости от машины.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я бы даже убрала эту фразу)

}
```

## Мьютекс чтения/записи `sync.RWMutex`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

бэктики в заголовках не надо. Мы это плохо обрабатываем.


## Мьютекс чтения/записи `sync.RWMutex`

Мы говорили о мьютексах ранее. Как уже было сказано, мьютексы защищают данные, предотвращая состояние гонки. Примитив синхронизации `sync.Mutex` блокирует доступ к данным для всех других горутин, пока текущая горутина не завершит свою работу с этими данными. Однако это не всегда оптимально. Что, если `10` горутин одновременно читают данные? Если использовать `sync.Mutex`, то чтение окажется последовательным. В действительности эти `10` горутин не мешают друг другу. Может быть, тогда вообще обойтись без мьютекса? Это правильное решение в случае, когда никто эти данные не пишет. В случае, когда есть писатель, хотелось бы одновременно читать, но блокировать горутину на запись. Примитив `sync.RWMutex` решает эту задачу.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мы говорили о мьютексах ранее.

Добавь плиз на тот абзац якорь и здесь сделай ссылку.

* `RUnlock`. Снимает блокировку `Rlock`.


Примитив синхронизации `sync.RWMutex` оптимальнее, так как множественные чтения могут выполняться параллельно. Чтобы убедиться в этом, рассмотрим пример. В нем мы работаем с двумя кешами: `CacheRWMutex` и `CacheMutex`. Первый использует мьютекс `RWMutex`, второй — `Mutex`. Чем больше читателей и меньше писателей, тем больше выигрыш по производительности.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

оптимальнее

лучше так не говорить, многие докапываются)


## Захват глобальных переменных горутиной

Существует одна известная проблема захвата глобальных переменных горутиной. Рассмотрим пример. На момент написания курса проблема все еще существовала. Для проверки этого кода использовался компилятор Go 1.26. Возможно, что на более новых версиях поведение будет изменено.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"одна" - лишнее слово

}

// Функция worker проверяет свою часть матрицы
func worker(input *[elementsNumber]equipmentT, min int, max int) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь elementsNumber является частью типа. Не имеет ли смысл передавать параметр через слайс []equipmentT - более обобщенно? Насколько я знаю, для го это более идиоматичный способ, чем передача по указателю.


// Функция check проверяет имя оборудования
func check(name string) int {
var invalidSymbols = []rune("!@#$%^&*()")
Copy link
Copy Markdown
Contributor

@Microvenator Microvenator Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check() вызывается для каждого элемента огромной матрицы. И при каждом вызове создается срез invalidSymbols. Более того, затем в цикле по строке вызывается Contains() по этому срезу, который работает за O(N). Давай что-нибудь более эффективное. Из серии unicode.IsPunct, strings.ContainsAny или что там у вас еще есть. И вынесения строки в глобальные константы, чтобы каждый раз не аллоцировать.

}

// Функция worker проверяет свою часть матрицы
func worker(input *[elementsNumber]equipmentT, min int, max int) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я так понимаю, в гошке принято передавать слайсы вместо указателя на массив и пары индексов.

}(min, max)
min = max
max = min + chunk
if max > elementsNumber {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У тебя очень многословный код. Многие конструкции пишутся лаконичнее.

Пример. Было:

if max > elementsNumber {
    max = elementsNumber
}

Стало:

max := min(elementsNumber, max)

Кстати это нормально, что у тебя переменная max называется так же, как встроенная функция?

}
wg.Wait()
t2 = time.Now()
resTime = t2.Sub(t1).Milliseconds()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Могу ошибаться, но кажется, time.Since(start) (где start := time.Now()) - более идиоматично.

"time"
)

const elementsNumber = 1000000
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1_000_000 ?

func main() {
cpuNumber, err := cpu.Counts(false)
if err != nil {
panic(err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

почему паника, а не что-нибудь вида log.Fatal?

if err != nil {
panic(err)
}
fmt.Printf("cpuNumber = %d", cpuNumber)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В конце строки разве не нужно \n ставить? printf же сам это не поставит?

)

func main() {
cpuNumber, err := cpu.Counts(false)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Надо добавить, что false нужен, чтобы получить количество физических ядер, а true — логических с учетом Hyper-Threading

Используйте `select-case` внутри бесконечного цикла, чтобы записывать те сообщения, которые придут раньше. В случае, если придет признак конца мониторинга, прервите цикл. Помните, что `break` внутри `select-case` выполняет свою задачу. Он не прервет цикл. Чтобы выйти из цикла, воспользуйтесь `break` по метке. {.task_hint}

```go {.task_answer}
package main
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

У меня локально этот код работает странно. У тебя случайно дэдлока нет?

monitorServer() пишет в небуферизованные каналы healthCh и alertCh. А statistics() читает из них и запускается после того, как analyze() типа поехала дальше.

Если не дэдлок, но энивэй это циклическая зависимость. monitorServer() ничего не запишет в monitorStopCh, пока не завершит свой цикл, но он не может, если statistics не прочитает из других каналов достаточно шустро.

}
start := time.Now()
for i := 0; i < readers; i++ {
wg.Add(1)
Copy link
Copy Markdown
Contributor

@Microvenator Microvenator Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Почему не wg.Add(readers) перед циклом? Ты сам вроде писал, что так правильно.

func (c *CacheRWMutex) get(key string) string {
c.rwMutex.RLock()
defer c.rwMutex.RUnlock()
time.Sleep(1 * time.Millisecond)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

return c.data[key]
}

func (c *CacheRWMutex) set(key, value string) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ты пишешь очень многословный код, уже говорила об этом. Пиши более обобщенный.

CacheRWMutex и CacheMutex похожи. Заведи структуру с интерфейсом для лока или просто параметризуй бенчмарк. Кода станет в два раза меньше.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type Locker interface {
    Lock()
    Unlock()
    RLock()   // Для обычного Mutex это будет просто Lock()
    RUnlock() // Для обычного Mutex это будет просто Unlock()
}

type GenericCache struct {
    data   map[string]string
    locker sync.Locker // Или кастомный интерфейс для RLock
}


// Сценарий 1: много читателей (80%), мало писателей (20%)
fmt.Println("1. Много читателей (80%), мало писателей (20%):")
rwTime := benchmark("RWMutex", 8, 2, ops, true)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Юзер еще не в курсе, как правильно делать бенчмарки. Думаю, надо ему сказать, что для единообразия запуска в песочнице мы все бахнули в мейн. Но по-хорошему должен быть нативный гошный бенчмарк. testing.B + go test -bench или как там у вас)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants