просто нереально полезные видео по многопоточности. Рекомендую всем знакомым, кто учит го. Огромное спасибо. Очень легкая подача, все понятно. Ты святой человек)
Бро лучше тебя никто не объясняет. Честно, с тобой я начал искренне понимать глубокие аспекты го. Желаю тебе и твоему каналу роста и популярности, и, надеюсь, ты создашь курс по общим паттернам и структуре сервисов в го потому, что с твоим обьяснением это было бы великолепно. С меня лайк и подписка!
спасибо за старания. Можно критику? Не шатай, плз, туда-сюда вверх-вниз код. Захвати один кусок кода и его разбирай, не дрыгая, если нет необходимости показывать разные части кода, которые не влезают в экран. upd: досмотрел. Чел, видос бомбовый! Спасибо еще раз
Спасибо за уроки, с удовольствием смотрю Вопрос про 15 секунд 1000 раз по одной наносекунде (10 в -9 степени) получается 1 микросекунда (10 в -6 степени) Как смогли получится секунды в примере без горутин - загадка У меня на m1 вот такие результаты: writeWithoutConcurrent: 1000 0.000464875 writeWithoutMutex: 986 0.000581708 writeWithMutex: 1000 0.000557375 То есть все колеблется около стат погрешности
Вот данные с другой машины (MacOS Quad-Core Intel i5) writeWithoutConcurrent: 1000 0.00042488 writeWithoutMutex: 993 0.000692333 writeWithMutex: 1000 0.000558311 То же все в пределах погрешностей
Вызов функции слип наверняка занимает больше 1 наносекунды, так что там на вызов тратится больше времени чем на слип. А насчет ваших результатов, то видимо дело в бОльшем количестве логических ядер или более производительном железе.
@@slayers2966 у автора результат порядка 5 секунд (округлим 15 секунд в меньшую сторону) у меня результат порядка 0.0005 секунд, это в 10к раз быстрее, такую разницу не обьяснить расходом на вызов функции sleep и разницей в железе. Даже просто интуитивно пробежать пустой цикл на 1к итераций за 15 секунд это очень медленно для любого языка и железа. Проверьте на вашей машине, интересно какие результаты получатся Я думаю, что это скорее связано с ос виндоус и/или багом в версии го, которая у автора
Я скопировал код функции, которая на видео выполнялась 15 секунд с вашего гит хаба. У меня она выполнилась за 2 десятитысячные секунды ( 0.0002 ), после протестировал функцию writeWithMutex, где мы для избежания data race лочили блок кода с записью в переменную, она у меня выполнилась за 5 десятитысячных ( в два с половиной раза медленнее). Я плохо понимаю, для чего мы запускаем кучу горутин, если он все все-равно становятся в очередь и ждут своего времени на лок. Чем это лучше обычного цикла с поочередным выполнением? В нашем случае мы получили даже обратный эффект, так-как нам нужно инициализировать 1000 горутин. Также, как я понимаю, когда горутина спит наносекунду управление берет на себя другая горутина и выходит так, что первая горутина спит больше наносекунды и так постоянно (можете меня поправить, просто предположение). Также я не понимаю, как функция в которой мы ждем наносекунду и добавляем единицу ( выполняется в простом цикле ) выполняется 15 секунд. За 1000 итераций команда time.Sleep(time.Nanosecond) в сумме даст одну милионную секунды, а все остальное время будет занимать операция добавления? То есть 1000 операций добавления выполняются (15 - 1 / 1 000 000) секунды? В этом моменте я вообще выпал =) Я новичок, наверное многого не понимаю, буду благодарен за ответ =)
Вы верно подметили, что как то уж очень долго выполнялся данный код. Я пошел разбираться, и оказалось, что это проблема в реализации работы на Windows, там таймер работает не очень корректно. Тут можно глянуть подробнее: github.com/golang/go/issues/44343 Также я запустил данный код на MacOs, и действительно код с циклом выполнился в 2 раза быстрей, чем с mutex. Насчет большого количества горутин вы абсолютно правы, много горутин - не всегда самый лучший по скорости выполнения результат. В видеоуроке, где я рассказываю про паттерн workersPool используется код, где количество горутин гораздо меньше, а скорость выполнения гораздо лучше. При выборе количества горутин очень важно понимать профиль работы вашего кода: это может быть cpu bounded или i/o bounded работа. В случае с cpu bounded (обычно это просто какие то вычисления), как например в нашем цикле с инкрементом - нет смысла запускать 1000 горутин, ибо на их менеджмент у планировщика уходит много ресурсов и времени, проще и быстрее инкрементить счетчик в одной горутине. Но если у вас очень много I/O bounded нагрузки (запросы к бд, сетевые запросы), то тут основная работа не выполняется вашим процессором, а вы просто находитесь в ожидании ответа (я попытался time.sleep имитировать это время ожидания). И вот тут то вы и можете почуствовать преимущество конкуретной работы. Простой пример - нужно сделать 5 запросов в разные сервисы. Собрать результат и куда то его вывести/отправить. Каждый запрос отвечает по 1 секунде. Если делать последовательно, то вы будете выполнять это минимум 5 секунд. Если запустить 5 запросов в отдельных горутинах, то вы можете уложиться за +-1 секунду. В общем самое важное - это понимать, когда вы можете распараллелить работу, а когда нет, и как избегать ошибок при параллельной работе) Еще хотел про сравнение работы мьютекса и без мьютекса дописать: если брать пример с вышеописанными 5ю запросами - то в первом случае вы выполняете последовательно всю работу: создание переменных, выполение запроса, ожидание ответа, запись ответа в переменную и потом по новой для другого запроса. Когда вы запускаете 5 запросов параллельно, скорее всего вы будете использовать mutex только в момент записи результатов в какой-то общий объект. т.е. здесь у вас будет только очередь на запись результатов, а в первом случае очередь на все действия.
Для чего ты в участок кода между mu.Lock() и mu.Unlock() добавляешь time.sleep(), когда рассказываешь про RWMutex, конечно оно так дольше будет работать) если убрать, то те же ~0.05 сек, что и до этого) ты как будто искусственно увеличиваешь время)
Сколько бы я не смотрел видео про конкурентность все говорят что 1 горутина занимает 1 ядро процессора, но это не верно в Go встренный планировщик горутин и он позволяет запускать хоть 10к горутин на 1 ядре
Все верно. Тут скорее про то, что параллельно выполняющихся горутин с cpu bound нагрузкой может быть не больше чем логических ядер процессора. Если есть какая-то io-bound нагрузка (сетевые запросы например), то на 1 ядре можно запустить и 10к горутин, просто планировщик будет отправлять запрос, переключаться на другую горутину, пока от 1 горутины не придет ответ и так для всех
Наконец-то я начал понимать, как работают мьютексы и WaitGroup, спасибо тебе) UPD. Тем не менее я не понимаю, как так быстро выполняются 1к операций если при Lock одна горутина блокирует значение, пока сама его не поменяет, а другие стоят в очереди. Потом другая блокирует, меняет, а остальные- ждут. Это представляется так, будто горутин и нет вовсе, а значение просто переписывается как в обычном цикле, только почему-то в х1000+ раз быстрее Хз, массу статей про горутины почитал, но не могу себе это как-то визуально представить.
Насчет скорости выполнения, тут зависит от того, что мы сравниваем. Если сравнить параллельную обработку и непараллельную, то в этом примере параллельная очень сильно выигрывает в скорости (или мы спим 1000 наносекунд в одной горутине, или по 1 наносекунде в 1000 горутинах почти одновременно). Если сравнивать 2 параллельные обработки между собой, но одна из них использует mutex, то за счет того, что горутины действительно выстраиваются в очередь, это работает медленнее (writeWithoutMutex отработала быстрей, чем writeWithMutex в 5-6 раз). Если сделать пример без time.Sleep, то обычный цикл с инкрементом в одной горутине отработает быстрей, так как пример с 1000 горутинами будет замедятся переключениеями между горутинами)
я вот честно говоря не понял :( Если mutex блокирует горутину и они начинают выполнятся по очереди, то почему это отличается от обычного выполнения цикла без горутин ? :(
Что правильно выбирать WaitGroup или слушать канал в SELECT? Как я понимаю в SELECT мы можем завершить выполнение горутин раньше. Например у меня есть 1000 сайтов, на которые надо сходить, я запускаю 1000 горутин который делают get запрос, и получают статус. Но для отчета хотелось бы понять, какие из 1000 сайтов не ответили. Тогда тут наверное стоит в http.Client добавить Timeout. Как бы Вы сделали?
Я бы наверное создал wg с 1000 задач, и в каждой горутине через select ждал либо ответа от http запроса, либо завершения контекста с таймаутом (в случае с http запросом это таймаут и он внутри сам вернет ошибку ). И при получении ответа/окончания таймаута завершал бы задачу в wg, где в случае таймаута или ошибки логировал бы это куда-нибудь. Если у нас http запрос отвалится по таймауту, то мы выйдем из горутины также быстро, если бы делали это через селект
Прошел все твои ролики, надо идти дальше. Какие темы нужно изучать следующими, чтоб не прыгать между десятками каналов и не лезть в ролики, которые я пока не потяну?
Мне еще по конкурентности осталось доделать: каналы, select, context. Далее уже зависит от направления, для веба обычно изучают пакет net и net/http. И дальше уже какие-то смежные инструменты, типо работы с бд, кэш, докер, CI, фреймворки. Вот такой roadmap есть кстати, github.com/Alikhll/golang-developer-roadmap
А почему бы нам с таким раскладом везде не использовать RWMutex, даже когда обновляем счетчик? Или в таком случае счетчик не будет лочиться на чтение в горутинах выше?
Счетчик будет лочиться, но если у нас преобладает количество изменений счетчика перед количеством чтений, то производительнее будет использовать обычный mutex. Почему? Потому что RW mutex содержит в себе 2 очереди: записывающие и читающие горутины и проверять 2 очереди при записи дольше и нет смысла. Поэтому если мы только пишем, то быстрее будет обычный mutex. Если пишем и читаем примерно одинаково, или же больше читаем, то тогда есть смысл RWMutex
Я правильно понимаю, что mu.Lock() блокирует обращение к именованной области памяти? А если у меня происходит изменение счетчика в разных местах одной го рутины, я везде должен лочить внутри одной го рутины? Если где-то в другом месте изменения счетчика я не поставил лок, го рутина все равно будет вставать в очередь, если область памяти залочена другой го рутиной? Наверное объяснил сумбурно) и спасибо за огромный и полезный труд
mu.lock блокирует не область памяти, а меняет состояние самого мьютекса. Горутины, которые используют этот мьютекс ориентируются именно на состояние мьютекса (залочен или не залочен). Если в других горутинах мы не используем мьютекс вообще или используем другой мьютекс, то они ничего не будут знать о нашем первом мьютексе и будут порождать dateRace. Именно поэтому очень важно передавать указатель на мьютекс во все горутины, которые используют общий ресурс, чтобы они все сверялись только на состояние одного мьютекса. Проще говоря: параллельная запись без мьютекса - dataRace, параллельная запись с разными мьютексами - dataRace, ибо горутины становятся не в общую очередь, а в 2 разные, которые ничего не знают друг о друге
Насчет нескольких записей в одной горутине есть 2 варианта: 1. Лочить мьютекс на все горутину 2. Лочить только участки с изменением счетчика Первый вариант хуже, ибо держит лок очень долго, я позже выпущу видос с объяснением этой проблемы Второй вариант лучше, ибо лочит мьютекс только во время полезной работы, но приходится лочить несколько раз. Если лень писать несколько локов подряд, можно конечно попробовать вынести в функцию и переиспользовать)
Спасибо за видео) Такой вопрос, в последнем примере с RWMutex ты сделал Wg.Add(100) до цикла for, я решил попробовать инкрементировать этот счетчик в цикле, но получил ошибку "WaitGroup is reused before previous Wait has returned", но когда я добавил wg.Add(1) в начале каждой анонимной горутины, то все сработало корректно. Можешь подсказать, почему так происходит?
А можно ссылку на итоговый вариант? Я у себя не смог именно эту ошибку воспроизвести. Предположил несколько вариантов: В цикле 50 итераций, но добавляются 2 горутины на каждой итерации, значит в цикле на каждой итерации нужно было делать wg.Add(2) или 2 раза wg.Add(1) Вариант с wg.Add внутри горутины опасный, ибо они могут не успеть себя добавить в ожидание к моменту завершения ожидания - таким образом мы просто не будем ждать эти горутины и завершим программу. И вот еще такое нашел: stackoverflow.com/questions/39800700/waitgroup-is-reused-before-previous-wait-has-returned Выглядит как то, о чем я говорил выше - горутины пытаются добавить себя в список ожидания после того как вызвали wait
Всем привет) вижу что канал забросили, но вдруг кто-то ответит)) Автор говорит что можно запустить количество горутин столько, сколько у тебя ядер, начинаю гуглить и там пишут что можно запустить неограниченное кол-во горутин🙆♂ Может кто-то пояснить?🤯
По факту количество горутин ограниченно оперативной памятью, т.к. выделяется стек под каждую ~2кб (это было отмеченно в предыдущем видео) плюс нужно учитывать сколько дополнительной памяти будет тратиться на "работу" в горутине. Но если наштамповать, например, 100 горутин, при этом имея 8 процессоров, то код будет выполняться параллельно только в 8 "потоков", остальные 92 горутины будут в ожидании очереди, т.е. конкурентное выполнение как в схеме из предыдущего видео с одной лишь разницей, что конкуренция не на одном процессоре, а на 8ми. Тут профит будет зависить от характера работы, которая выполняется в горутинах. Например, если там выполняются блокирующие вызовы, например сетевое обращение к удалённому ресурсу, тут может быть и профит при переключении шедулером на другую горутину, но если в горутинах какие то математические вычисления, т.е. рутинная работа процессора, то профита не будет, а скорее всего будут даже потери производительности на переключение конекста между горутинами (даже учитывая что переключение контекста горутин на много "дешевле" переключения контекста тредов ОС - на переключении горутин тоже есть потери производительности).
Дата рейс - это не гонка данных, это скорее гонка доступа к данным. Если вы это пишите с идеей о том, что не нужно использовать англицизмы в языке, дак тогда выкиньте из русского языка все остальные заимствованные слова. Ваше имя, например, из древнееврейского языка, а не из русского. Не Илья, а Иван xD
желаю чтобы твой канал стал №1 по Golang
Спасибо, буду стараться)
автор куда-то пропал, но он действительно лучший
Очень надеюсь, ты дойдешь до конца и запишешь все планируемые уроки!
просто нереально полезные видео по многопоточности. Рекомендую всем знакомым, кто учит го. Огромное спасибо. Очень легкая подача, все понятно. Ты святой человек)
Бро лучше тебя никто не объясняет. Честно, с тобой я начал искренне понимать глубокие аспекты го.
Желаю тебе и твоему каналу роста и популярности, и, надеюсь, ты создашь курс по общим паттернам и структуре сервисов в го потому, что с твоим обьяснением это было бы великолепно.
С меня лайк и подписка!
самое лучшее объяснение, получше даже платных уроков! Вы гений! Я пришла после долгих попыток понять RWMutexы
как успехи в golang сейчас?
Очень круто и подробно объясняешь, спасибо! Надеюсь, новые видео не заставят себя ждать, а то последнее по модулям было 6 месяцев назад =(
Грац с 500 подписчиков) Крутые видео 👍🏼
Спасибо, приятно)
Спасибо за уроки! Очень хорошо изложенная информация.
Вы очень доходчиво объясняете. Большое спасибо за качественные уроки!
По этому видео конкурентность в sql даже понятней становится)
Самое понятное объяснение про RWMutex. Спасибо
спасибо за старания. Можно критику? Не шатай, плз, туда-сюда вверх-вниз код. Захвати один кусок кода и его разбирай, не дрыгая, если нет необходимости показывать разные части кода, которые не влезают в экран. upd: досмотрел. Чел, видос бомбовый! Спасибо еще раз
Отличное объяснение! Пожалуйста продолжай)
Спасибо за уроки!
Это просто великолепие.
Спасибо наконецто понятно преемущество над другим языками, но отсутвие ооп все равно бесит)
Супер объяснение
Да уж 😄 15 сек 1000 итераций! Пора вам комп менять. 1000000 = ~0,2 с. Спасибо, отлично объясняешь!
Там задержка стоит)
Сбрось ее и сравни по времени
Вау!!! Вот это скорость!!!😀
спасибо, все р\понятно
Спасибо за уроки, с удовольствием смотрю
Вопрос про 15 секунд
1000 раз по одной наносекунде (10 в -9 степени) получается 1 микросекунда (10 в -6 степени)
Как смогли получится секунды в примере без горутин - загадка
У меня на m1 вот такие результаты:
writeWithoutConcurrent:
1000
0.000464875
writeWithoutMutex:
986
0.000581708
writeWithMutex:
1000
0.000557375
То есть все колеблется около стат погрешности
Вот данные с другой машины (MacOS Quad-Core Intel i5)
writeWithoutConcurrent:
1000
0.00042488
writeWithoutMutex:
993
0.000692333
writeWithMutex:
1000
0.000558311
То же все в пределах погрешностей
Вызов функции слип наверняка занимает больше 1 наносекунды, так что там на вызов тратится больше времени чем на слип. А насчет ваших результатов, то видимо дело в бОльшем количестве логических ядер или более производительном железе.
@@slayers2966 у автора результат порядка 5 секунд (округлим 15 секунд в меньшую сторону) у меня результат порядка 0.0005 секунд, это в 10к раз быстрее, такую разницу не обьяснить расходом на вызов функции sleep и разницей в железе. Даже просто интуитивно пробежать пустой цикл на 1к итераций за 15 секунд это очень медленно для любого языка и железа.
Проверьте на вашей машине, интересно какие результаты получатся
Я думаю, что это скорее связано с ос виндоус и/или багом в версии го, которая у автора
отлично
Я скопировал код функции, которая на видео выполнялась 15 секунд с вашего гит хаба. У меня она выполнилась за 2 десятитысячные секунды ( 0.0002 ), после протестировал функцию writeWithMutex, где мы для избежания data race лочили блок кода с записью в переменную, она у меня выполнилась за 5 десятитысячных ( в два с половиной раза медленнее). Я плохо понимаю, для чего мы запускаем кучу горутин, если он все все-равно становятся в очередь и ждут своего времени на лок. Чем это лучше обычного цикла с поочередным выполнением? В нашем случае мы получили даже обратный эффект, так-как нам нужно инициализировать 1000 горутин. Также, как я понимаю, когда горутина спит наносекунду управление берет на себя другая горутина и выходит так, что первая горутина спит больше наносекунды и так постоянно (можете меня поправить, просто предположение).
Также я не понимаю, как функция в которой мы ждем наносекунду и добавляем единицу ( выполняется в простом цикле ) выполняется 15 секунд. За 1000 итераций команда time.Sleep(time.Nanosecond) в сумме даст одну милионную секунды, а все остальное время будет занимать операция добавления? То есть 1000 операций добавления выполняются (15 - 1 / 1 000 000) секунды? В этом моменте я вообще выпал =)
Я новичок, наверное многого не понимаю, буду благодарен за ответ =)
Вы верно подметили, что как то уж очень долго выполнялся данный код. Я пошел разбираться, и оказалось, что это проблема в реализации работы на Windows, там таймер работает не очень корректно. Тут можно глянуть подробнее: github.com/golang/go/issues/44343
Также я запустил данный код на MacOs, и действительно код с циклом выполнился в 2 раза быстрей, чем с mutex.
Насчет большого количества горутин вы абсолютно правы, много горутин - не всегда самый лучший по скорости выполнения результат. В видеоуроке, где я рассказываю про паттерн workersPool используется код, где количество горутин гораздо меньше, а скорость выполнения гораздо лучше.
При выборе количества горутин очень важно понимать профиль работы вашего кода: это может быть cpu bounded или i/o bounded работа. В случае с cpu bounded (обычно это просто какие то вычисления), как например в нашем цикле с инкрементом - нет смысла запускать 1000 горутин, ибо на их менеджмент у планировщика уходит много ресурсов и времени, проще и быстрее инкрементить счетчик в одной горутине. Но если у вас очень много I/O bounded нагрузки (запросы к бд, сетевые запросы), то тут основная работа не выполняется вашим процессором, а вы просто находитесь в ожидании ответа (я попытался time.sleep имитировать это время ожидания). И вот тут то вы и можете почуствовать преимущество конкуретной работы.
Простой пример - нужно сделать 5 запросов в разные сервисы. Собрать результат и куда то его вывести/отправить. Каждый запрос отвечает по 1 секунде.
Если делать последовательно, то вы будете выполнять это минимум 5 секунд.
Если запустить 5 запросов в отдельных горутинах, то вы можете уложиться за +-1 секунду.
В общем самое важное - это понимать, когда вы можете распараллелить работу, а когда нет, и как избегать ошибок при параллельной работе)
Еще хотел про сравнение работы мьютекса и без мьютекса дописать: если брать пример с вышеописанными 5ю запросами - то в первом случае вы выполняете последовательно всю работу: создание переменных, выполение запроса, ожидание ответа, запись ответа в переменную и потом по новой для другого запроса.
Когда вы запускаете 5 запросов параллельно, скорее всего вы будете использовать mutex только в момент записи результатов в какой-то общий объект. т.е. здесь у вас будет только очередь на запись результатов, а в первом случае очередь на все действия.
@@thisisit7267 Спасибо за ответ, стало яснее =)
Для Ubuntu ---> поменяйте time.Sleep(time.Nanosecond) на time.Sleep(time.Millisecond * 15) и будете чувствовать разницу.
👍👍👍
Для чего ты в участок кода между mu.Lock() и mu.Unlock() добавляешь time.sleep(), когда рассказываешь про RWMutex, конечно оно так дольше будет работать) если убрать, то те же ~0.05 сек, что и до этого) ты как будто искусственно увеличиваешь время)
Сколько бы я не смотрел видео про конкурентность все говорят что 1 горутина занимает 1 ядро процессора, но это не верно в Go встренный планировщик горутин и он позволяет запускать хоть 10к горутин на 1 ядре
Все верно. Тут скорее про то, что параллельно выполняющихся горутин с cpu bound нагрузкой может быть не больше чем логических ядер процессора. Если есть какая-то io-bound нагрузка (сетевые запросы например), то на 1 ядре можно запустить и 10к горутин, просто планировщик будет отправлять запрос, переключаться на другую горутину, пока от 1 горутины не придет ответ и так для всех
@@thisisit7267 по моему ты забыл приложить под видео ссылку "на сбор благодарностей" 🙃
Наконец-то я начал понимать, как работают мьютексы и WaitGroup, спасибо тебе)
UPD. Тем не менее я не понимаю, как так быстро выполняются 1к операций если при Lock одна горутина блокирует значение, пока сама его не поменяет, а другие стоят в очереди. Потом другая блокирует, меняет, а остальные- ждут. Это представляется так, будто горутин и нет вовсе, а значение просто переписывается как в обычном цикле, только почему-то в х1000+ раз быстрее
Хз, массу статей про горутины почитал, но не могу себе это как-то визуально представить.
Насчет скорости выполнения, тут зависит от того, что мы сравниваем. Если сравнить параллельную обработку и непараллельную, то в этом примере параллельная очень сильно выигрывает в скорости (или мы спим 1000 наносекунд в одной горутине, или по 1 наносекунде в 1000 горутинах почти одновременно).
Если сравнивать 2 параллельные обработки между собой, но одна из них использует mutex, то за счет того, что горутины действительно выстраиваются в очередь, это работает медленнее (writeWithoutMutex отработала быстрей, чем writeWithMutex в 5-6 раз).
Если сделать пример без time.Sleep, то обычный цикл с инкрементом в одной горутине отработает быстрей, так как пример с 1000 горутинами будет замедятся переключениеями между горутинами)
Стоит объяснить почему на практике лучше использовать не мьютекс, а указатель на мьютекс
я вот честно говоря не понял :( Если mutex блокирует горутину и они начинают выполнятся по очереди, то почему это отличается от обычного выполнения цикла без горутин ? :(
Офигенные советы, как defer wg.Done() отдельное спасибо за это.
Что правильно выбирать WaitGroup или слушать канал в SELECT? Как я понимаю в SELECT мы можем завершить выполнение горутин раньше. Например у меня есть 1000 сайтов, на которые надо сходить, я запускаю 1000 горутин который делают get запрос, и получают статус. Но для отчета хотелось бы понять, какие из 1000 сайтов не ответили. Тогда тут наверное стоит в http.Client добавить Timeout. Как бы Вы сделали?
Я бы наверное создал wg с 1000 задач, и в каждой горутине через select ждал либо ответа от http запроса, либо завершения контекста с таймаутом (в случае с http запросом это таймаут и он внутри сам вернет ошибку ). И при получении ответа/окончания таймаута завершал бы задачу в wg, где в случае таймаута или ошибки логировал бы это куда-нибудь. Если у нас http запрос отвалится по таймауту, то мы выйдем из горутины также быстро, если бы делали это через селект
Непонятно для чего в данном примере мы лочим горутины для чтения. Ведь данные там в любом случае будут в рандом порядке
Прошел все твои ролики, надо идти дальше. Какие темы нужно изучать следующими, чтоб не прыгать между десятками каналов и не лезть в ролики, которые я пока не потяну?
Мне еще по конкурентности осталось доделать: каналы, select, context. Далее уже зависит от направления, для веба обычно изучают пакет net и net/http. И дальше уже какие-то смежные инструменты, типо работы с бд, кэш, докер, CI, фреймворки. Вот такой roadmap есть кстати, github.com/Alikhll/golang-developer-roadmap
@@thisisit7267 В качестве просьбы по web, было бы классно использовать gorilla mux. Спасибо за видео!
А если ошибка при counter++ горутин не сможет сделать анлок и все зависнет ?
будет deadlock
а что у тебя написано в первой функции перед "exit"? в исходном коде такого нет
А почему бы нам с таким раскладом везде не использовать RWMutex, даже когда обновляем счетчик? Или в таком случае счетчик не будет лочиться на чтение в горутинах выше?
Счетчик будет лочиться, но если у нас преобладает количество изменений счетчика перед количеством чтений, то производительнее будет использовать обычный mutex. Почему? Потому что RW mutex содержит в себе 2 очереди: записывающие и читающие горутины и проверять 2 очереди при записи дольше и нет смысла. Поэтому если мы только пишем, то быстрее будет обычный mutex. Если пишем и читаем примерно одинаково, или же больше читаем, то тогда есть смысл RWMutex
@@thisisit7267 спасибо!
Я правильно понимаю, что mu.Lock() блокирует обращение к именованной области памяти? А если у меня происходит изменение счетчика в разных местах одной го рутины, я везде должен лочить внутри одной го рутины? Если где-то в другом месте изменения счетчика я не поставил лок, го рутина все равно будет вставать в очередь, если область памяти залочена другой го рутиной? Наверное объяснил сумбурно) и спасибо за огромный и полезный труд
mu.lock блокирует не область памяти, а меняет состояние самого мьютекса. Горутины, которые используют этот мьютекс ориентируются именно на состояние мьютекса (залочен или не залочен).
Если в других горутинах мы не используем мьютекс вообще или используем другой мьютекс, то они ничего не будут знать о нашем первом мьютексе и будут порождать dateRace.
Именно поэтому очень важно передавать указатель на мьютекс во все горутины, которые используют общий ресурс, чтобы они все сверялись только на состояние одного мьютекса.
Проще говоря: параллельная запись без мьютекса - dataRace, параллельная запись с разными мьютексами - dataRace, ибо горутины становятся не в общую очередь, а в 2 разные, которые ничего не знают друг о друге
Насчет нескольких записей в одной горутине есть 2 варианта:
1. Лочить мьютекс на все горутину
2. Лочить только участки с изменением счетчика
Первый вариант хуже, ибо держит лок очень долго, я позже выпущу видос с объяснением этой проблемы
Второй вариант лучше, ибо лочит мьютекс только во время полезной работы, но приходится лочить несколько раз. Если лень писать несколько локов подряд, можно конечно попробовать вынести в функцию и переиспользовать)
Спасибо за видео) Такой вопрос, в последнем примере с RWMutex ты сделал Wg.Add(100) до цикла for, я решил попробовать инкрементировать этот счетчик в цикле, но получил ошибку "WaitGroup is reused before previous Wait has returned", но когда я добавил wg.Add(1) в начале каждой анонимной горутины, то все сработало корректно. Можешь подсказать, почему так происходит?
А можно ссылку на итоговый вариант? Я у себя не смог именно эту ошибку воспроизвести.
Предположил несколько вариантов:
В цикле 50 итераций, но добавляются 2 горутины на каждой итерации, значит в цикле на каждой итерации нужно было делать wg.Add(2) или 2 раза wg.Add(1)
Вариант с wg.Add внутри горутины опасный, ибо они могут не успеть себя добавить в ожидание к моменту завершения ожидания - таким образом мы просто не будем ждать эти горутины и завершим программу.
И вот еще такое нашел: stackoverflow.com/questions/39800700/waitgroup-is-reused-before-previous-wait-has-returned Выглядит как то, о чем я говорил выше - горутины пытаются добавить себя в список ожидания после того как вызвали wait
@@thisisit7267 спасибо, разобрался)
Всем привет) вижу что канал забросили, но вдруг кто-то ответит))
Автор говорит что можно запустить количество горутин столько, сколько у тебя ядер, начинаю гуглить и там пишут что можно запустить неограниченное кол-во горутин🙆♂
Может кто-то пояснить?🤯
По факту количество горутин ограниченно оперативной памятью, т.к. выделяется стек под каждую ~2кб (это было отмеченно в предыдущем видео) плюс нужно учитывать сколько дополнительной памяти будет тратиться на "работу" в горутине. Но если наштамповать, например, 100 горутин, при этом имея 8 процессоров, то код будет выполняться параллельно только в 8 "потоков", остальные 92 горутины будут в ожидании очереди, т.е. конкурентное выполнение как в схеме из предыдущего видео с одной лишь разницей, что конкуренция не на одном процессоре, а на 8ми. Тут профит будет зависить от характера работы, которая выполняется в горутинах. Например, если там выполняются блокирующие вызовы, например сетевое обращение к удалённому ресурсу, тут может быть и профит при переключении шедулером на другую горутину, но если в горутинах какие то математические вычисления, т.е. рутинная работа процессора, то профита не будет, а скорее всего будут даже потери производительности на переключение конекста между горутинами (даже учитывая что переключение контекста горутин на много "дешевле" переключения контекста тредов ОС - на переключении горутин тоже есть потери производительности).
кстати говоря, автор канала уже ответил на подобный вопрос, прямо под Вашим (отсортируйте комментарии по новизне)
1 горутина не равно 1 ядро.
Не дата рейс, а «гонка данных»
Дата рейс - это не гонка данных, это скорее гонка доступа к данным. Если вы это пишите с идеей о том, что не нужно использовать англицизмы в языке, дак тогда выкиньте из русского языка все остальные заимствованные слова. Ваше имя, например, из древнееврейского языка, а не из русского. Не Илья, а Иван xD
@@bezborodovanton будем изучать язык программирования "ИДТИ" xD