Conversation
| fmt.Printf("validNumber=%d, invalidNumber=%d, unknownNumber=%d\n", | ||
| validNumber, invalidNumber, elementsNumber-validNumber-invalidNumber) | ||
| } | ||
| func main() { |
There was a problem hiding this comment.
Здесь и далее. Форматирование кода. Нет пробела между функциями.
|
|
||
| Метод `Add` должен быть вызван перед запуском горутин, а не внутри них! В противном случае мы не можем быть уверены, что вызвали его до метода `Wait`. | ||
|
|
||
| Следующий пример демонстрирует использование `sync.WaitGroup`. Программа должна обработать матрицу оборудования размером *1 млн.* элементов. Каждый элемент представляет собой структуру из имени оборудования и признака того, что оно валидно. Допустимым именем оборудования является любая строка, в которой отсутствуют символы `"!@#$%^&*()"`. В противном случае имя является недопустимым. Необходимо проставить признак допустимости имени для каждого элемента. |
There was a problem hiding this comment.
в которой отсутствуют символы
"!@#$%^&*()"
Когда хочешь получить валидную строку, то описываешь разрешенные символы, а не запрещенные. Например, разрешены alphanum и пробелы. А в твоем случае получается, что также разрешены непечатаемые символы, управляющие символы, бэктики, знак равно и тд и тп.
| @@ -0,0 +1,870 @@ | |||
| # Глава 19. Управление параллельным исполнением | |||
|
|
|||
| В этой главе мы познакомимся со счетчиком `sync.WaitGroup`. Он позволяет дождаться окончания работы нескольких горутин. До сих пор мы использовали для этого каналы. Счетчик `sync.WaitGroup` предоставляет более удобные и надежные средства для этих целей. | |||
There was a problem hiding this comment.
и надежные средства
из формулировки складывается впечатление, что каналы работают нестабильно) Ты же имел ввиду надежные в плане недопускания ошибок в коде? Лучше переформулируй
|
|
||
| Также отметим один важный момент. Функция `runtime.NumCPU` подсчитывает не только количество физических ядер, но и количество логических. Для некоторых задач нужно знать именно число физических ядер, потому что распараллеливать задачи по логическим не имеет смысла. Иногда это может даже замедлить работу. Например, для тяжелых математических расчетов, имеющих большое количество операций с плавающей точкой. | ||
|
|
||
| Такие задачи плохо запускать параллельно на ядрах, которые имеют только один блок для операций с плавающей точкой (FPU). Потоки запущенные, на одном ядре, будут конкурировать за FPU. |
There was a problem hiding this comment.
Потоки запущенные, на одном ядре, будут конкурировать за FPU.
Потоки, запущенные на одном ядре, будут конкурировать за FPU.
И предлагаю добавить ссылку:
| cpuNumber = 6 | ||
| ``` | ||
|
|
||
| Понятное дело, что вывод индивидуален, в зависимости от машины. |
There was a problem hiding this comment.
Я бы даже убрала эту фразу)
| } | ||
| ``` | ||
|
|
||
| ## Мьютекс чтения/записи `sync.RWMutex` |
There was a problem hiding this comment.
бэктики в заголовках не надо. Мы это плохо обрабатываем.
|
|
||
| ## Мьютекс чтения/записи `sync.RWMutex` | ||
|
|
||
| Мы говорили о мьютексах ранее. Как уже было сказано, мьютексы защищают данные, предотвращая состояние гонки. Примитив синхронизации `sync.Mutex` блокирует доступ к данным для всех других горутин, пока текущая горутина не завершит свою работу с этими данными. Однако это не всегда оптимально. Что, если `10` горутин одновременно читают данные? Если использовать `sync.Mutex`, то чтение окажется последовательным. В действительности эти `10` горутин не мешают друг другу. Может быть, тогда вообще обойтись без мьютекса? Это правильное решение в случае, когда никто эти данные не пишет. В случае, когда есть писатель, хотелось бы одновременно читать, но блокировать горутину на запись. Примитив `sync.RWMutex` решает эту задачу. |
There was a problem hiding this comment.
Мы говорили о мьютексах ранее.
Добавь плиз на тот абзац якорь и здесь сделай ссылку.
| * `RUnlock`. Снимает блокировку `Rlock`. | ||
|
|
||
|
|
||
| Примитив синхронизации `sync.RWMutex` оптимальнее, так как множественные чтения могут выполняться параллельно. Чтобы убедиться в этом, рассмотрим пример. В нем мы работаем с двумя кешами: `CacheRWMutex` и `CacheMutex`. Первый использует мьютекс `RWMutex`, второй — `Mutex`. Чем больше читателей и меньше писателей, тем больше выигрыш по производительности. |
There was a problem hiding this comment.
оптимальнее
лучше так не говорить, многие докапываются)
|
|
||
| ## Захват глобальных переменных горутиной | ||
|
|
||
| Существует одна известная проблема захвата глобальных переменных горутиной. Рассмотрим пример. На момент написания курса проблема все еще существовала. Для проверки этого кода использовался компилятор Go 1.26. Возможно, что на более новых версиях поведение будет изменено. |
There was a problem hiding this comment.
"одна" - лишнее слово
| } | ||
|
|
||
| // Функция worker проверяет свою часть матрицы | ||
| func worker(input *[elementsNumber]equipmentT, min int, max int) { |
There was a problem hiding this comment.
Здесь elementsNumber является частью типа. Не имеет ли смысл передавать параметр через слайс []equipmentT - более обобщенно? Насколько я знаю, для го это более идиоматичный способ, чем передача по указателю.
|
|
||
| // Функция check проверяет имя оборудования | ||
| func check(name string) int { | ||
| var invalidSymbols = []rune("!@#$%^&*()") |
There was a problem hiding this comment.
check() вызывается для каждого элемента огромной матрицы. И при каждом вызове создается срез invalidSymbols. Более того, затем в цикле по строке вызывается Contains() по этому срезу, который работает за O(N). Давай что-нибудь более эффективное. Из серии unicode.IsPunct, strings.ContainsAny или что там у вас еще есть. И вынесения строки в глобальные константы, чтобы каждый раз не аллоцировать.
| } | ||
|
|
||
| // Функция worker проверяет свою часть матрицы | ||
| func worker(input *[elementsNumber]equipmentT, min int, max int) { |
There was a problem hiding this comment.
Я так понимаю, в гошке принято передавать слайсы вместо указателя на массив и пары индексов.
| }(min, max) | ||
| min = max | ||
| max = min + chunk | ||
| if max > elementsNumber { |
There was a problem hiding this comment.
У тебя очень многословный код. Многие конструкции пишутся лаконичнее.
Пример. Было:
if max > elementsNumber {
max = elementsNumber
}Стало:
max := min(elementsNumber, max)Кстати это нормально, что у тебя переменная max называется так же, как встроенная функция?
| } | ||
| wg.Wait() | ||
| t2 = time.Now() | ||
| resTime = t2.Sub(t1).Milliseconds() |
There was a problem hiding this comment.
Могу ошибаться, но кажется, time.Since(start) (где start := time.Now()) - более идиоматично.
| "time" | ||
| ) | ||
|
|
||
| const elementsNumber = 1000000 |
| func main() { | ||
| cpuNumber, err := cpu.Counts(false) | ||
| if err != nil { | ||
| panic(err) |
There was a problem hiding this comment.
почему паника, а не что-нибудь вида log.Fatal?
| if err != nil { | ||
| panic(err) | ||
| } | ||
| fmt.Printf("cpuNumber = %d", cpuNumber) |
There was a problem hiding this comment.
В конце строки разве не нужно \n ставить? printf же сам это не поставит?
| ) | ||
|
|
||
| func main() { | ||
| cpuNumber, err := cpu.Counts(false) |
There was a problem hiding this comment.
Надо добавить, что false нужен, чтобы получить количество физических ядер, а true — логических с учетом Hyper-Threading
| Используйте `select-case` внутри бесконечного цикла, чтобы записывать те сообщения, которые придут раньше. В случае, если придет признак конца мониторинга, прервите цикл. Помните, что `break` внутри `select-case` выполняет свою задачу. Он не прервет цикл. Чтобы выйти из цикла, воспользуйтесь `break` по метке. {.task_hint} | ||
|
|
||
| ```go {.task_answer} | ||
| package main |
There was a problem hiding this comment.
У меня локально этот код работает странно. У тебя случайно дэдлока нет?
monitorServer() пишет в небуферизованные каналы healthCh и alertCh. А statistics() читает из них и запускается после того, как analyze() типа поехала дальше.
Если не дэдлок, но энивэй это циклическая зависимость. monitorServer() ничего не запишет в monitorStopCh, пока не завершит свой цикл, но он не может, если statistics не прочитает из других каналов достаточно шустро.
| } | ||
| start := time.Now() | ||
| for i := 0; i < readers; i++ { | ||
| wg.Add(1) |
There was a problem hiding this comment.
Почему не wg.Add(readers) перед циклом? Ты сам вроде писал, что так правильно.
| func (c *CacheRWMutex) get(key string) string { | ||
| c.rwMutex.RLock() | ||
| defer c.rwMutex.RUnlock() | ||
| time.Sleep(1 * time.Millisecond) |
There was a problem hiding this comment.
Ты жэ конешно понимаешь, что любой time.Sleep драматически коверкает результаты замеров? И бенчмарк становится тыквой. Ты будешь мерить слипы и сайд-эффекты от них на мьютексты. А не накладные расходы мьютексов.
| return c.data[key] | ||
| } | ||
|
|
||
| func (c *CacheRWMutex) set(key, value string) { |
There was a problem hiding this comment.
Ты пишешь очень многословный код, уже говорила об этом. Пиши более обобщенный.
CacheRWMutex и CacheMutex похожи. Заведи структуру с интерфейсом для лока или просто параметризуй бенчмарк. Кода станет в два раза меньше.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Юзер еще не в курсе, как правильно делать бенчмарки. Думаю, надо ему сказать, что для единообразия запуска в песочнице мы все бахнули в мейн. Но по-хорошему должен быть нативный гошный бенчмарк. testing.B + go test -bench или как там у вас)
No description provided.