Как стать автором
Обновить

Комментарии 62

#1 Мантра про 3 поколения в любой ситуации

По-моему это почти всегда вопрос чисто для собеседования. Мне кажется, потому что Рихтера дальше сборщика мусора большинство не осилили.
В C# очень мало способов работать со сборщиком мусора и реально редки кейсы, когда знание про то, что он есть хоть как-то помогает. Другими словами, раньше ваш проект закроют, чем вы дойдёте до тонкой оптимизации кода под сборщик мусора.

Тут даже не про тонкую оптимизацию речь. Такие знания наиболее полезны, когда от пользователей повалили дампы с жалобами на «повисшую» софтиную. Раскопать, кто же наинвочил в UI'ный поток, без знания о сборке мусора практически невозможно, особенно если QA не может воспроизвести проблему, а напрямую отдебажить не представляется возможным ввиду объёма кода и кол-ва потоков.
UI'ный поток

Специфика конкретного UI-фреймворка. Следуйте гайдлайнам и не будет таких проблем. Понятно, что у разных UI-фреймворков свои тонкости.
Я никогда не работал с WPF и Unity, а в asp.net таких проблем не было и нет, там проблемы свои, но я за 10 лет ни разу не встречался с проблемами от GC в самых разных проектах.
Это не проблема фрэймворка — это проблема кода приложения. Многие фрэймворки требуют обращения к UI'ным объектам только в UI'ном потоке, просто потому что STAThread — тяжкое наследие COM'а. Во многих фрэймворках и библиотеках есть Invoke() и BeginInvoke()

Сейчас с async-await стало чуть легче писать подобный код.

Многие фрэймворки требуют обращения к UI'ным объектам только в UI'ном потоке, просто потому что STAThread — тяжкое наследие COM'а.
В iOS изначально можно было обращаться к UI из любого потока. Правда если так делать, то потом получаешь такие состояния UI, о которых даже подумать не мог.
Среди зеленых юниоров распространено мракобесие по поводу очевидных (только что прочитавшему книгу про память) вещей

Зачем вообще спрашивать джуна про количество поколений CG?

Потому что собеседующие не знают что спрашивать
Такие вопросы задают чтобы выявить хоть кого-то, кто интересуется языком да и программированием вообще. Не важно, что отверят — главное что-б не «а я учииил...» и глаза такие большие большие… у дядьки весом под центнер и седой бородой.
Нет, это вопрос из списка «10 банальных вопросов для собеседования программиста на языке C#». Это совсем не про любознательность и интерес к программированию.
Чтобы определить интерес к программированию надо смотреть как и что человек рассказывает про прошлую работу/опыт/проекты/дипломы и как он рассказывает про то, чем хотел бы заниматься. Для этого интервьюиру нужно иметь богатый опыт разработки и кругозор в технологиях, коим большая часть 23-летних тимлидов, разумеется, не обладает.

А вот и нет. Врать про прошлые проекты, где в реальности на совещаниях сидел да одну фичу годами точил может (почти) каждый. А спросишь его элементарный синтаксис — всё поплыл. Вы удивитесь, но примерно 80% на вопрос о garbage collector или GetHashCode не отвечают вообще ничего.


Кровавый энтерпрайз за дофига зарплаты.

Я не очень понимаю как можно врать про нюансы и сложности использования тех или иных технологических/архитектурных решений на прошлых проектах. Если человек легко может рассказать что, почему и как было использовано, то все ок.
Нет, потому что собеседующий хочет показать свою значимость и «крутизну».
Ну как сказать… Я вот почти сразу на грабли наступил. Дело было так. Детишки попросили соорудить игрушечный камин для кукольного домика, чтоб огонь горел как настоящий. У меня под рукой была платка Raspberry Pi да трехдюймовый мониторчик. Делов на вечер, но дело там осложнялось тем, что мониторчик был на шине SPI со специальным API и библиотекой. Короче, для упражнения я написал программку на C# и она там под mono крутилась, показывая в цикле фреймы из видео. Где-то кадров 15 в секунду я получил. Все работало, но падало через некоторое время с переполнением памяти, хотя утечек там на вид не было. Там вся программа-то полсотни строк была от силы. И решилось, как ни странно, вставкой небольшой паузы в несколько миллисекунд в цикл. Судя по всему, сборщик мусора банально не получал процессорного времени.

Если мониторчик типа ili9341, то для него есть fbtft и больше 15 кадров в секунду с него получить проблематично

Все гуд, только Вам очень повезло с зелеными джунами — те которые попадались мне (а последнее время ещё чаще стали попадаться) на подобные темы в таком ракурсе точно общаться не готовы… Справедливости ради я от них подобных нюансов, наверное, и не жду даже…

В статье вопросы не для джуна. А детали имплементации сборщика мусора в разных рантаймах могут не знать и синьоры.

неплохо бы знать, что эти разные рантаймы таки есть.
Ни один из рассмотренных вопросов начинающим нафиг не сдался.
А вы точно специалист?
Стек вызовов — структура данных, а именно стек, для хранения адресов возврата, для возвращения из функций. Стек вызовов — понятие больше логическое. Оно не регламентирует где и как должна храниться информация для возврата. Получается, что стек вызовов — самый обычный и родной нам стек т.е. Stack T
Каждое предложение, на мой взгляд, ложное.
Стек поддерживаемый CPU хранит еще значения регистров до изменения этих регистров новой функцией.
Может до JIT оно больше логическое, но вряд ли, но после JIT наверняка используется машино-зависимый стек поддерживаемый CPU, то есть точно не Stack T, то есть именно регламентируется где и как должна хранится информация для возврата, что бы CPU это понял.
Конечно если Stack T — вдруг стал виртуальной оберткой над поддерживаемым CPU стеком, то конечно это могло бы быть правдой, но нет.
А есть ещё архитектуры, отличные от x86, которые не хранят адреса возвратов. Например ARM, на котором .NET тоже уже вроде как работает.
Я не знаком с асемблером ARM. Там что приходится самому выдумывать где хранить адреса возвратов и текущий контекст состояния CPU? На ARM может тогда и подойдет Stack T, но может и на ARM проще указатель стека «двигать», что бы не использовать Stack T потому что наверняка у Stack T всякие излишние аллокации памяти есть?
Нет, там есть специальный регистр — LR (Link Register) — куда записывается адрес возврата. А его уже можно куда-нибудь потом переложить.

Иными словами, адрес возврата всё равно хранится в стеке, просто конкретное место хранения в кадре определяется не архитектурой процессора, а компилятором.

Почти. Адрес возврата хранится в специальном регистре, но чтобы его повторно использовать, его надо сохранить, например в стек. Этот механизм похож на какой-нибудь jmp в конце метода, когда инлайнится вызов другой функции, но обладает несколько большей гибкостью.
Когда я устраивался в качестве джуниора, на собеседовании были вопросы про сборку мусора (в т.ч. я рассказал про 3 поколения, о которых вы упомянули), как работает GC, boxing/unboxing и т.д. Я бы не сказал, что это что-то супер сложное… Обычно, такие вопросы не вызывают проблем у тех, кто дочитал книгу Рихтера :)
Не уверен что джун вообще должен знать о поколениях сборщика мусора. Да и Рихтера даже не обязательно читать и помнить все прочитанное. Вот дальше да, будет нужно.
Джуны джунам рознь. Никто не говорит, что это знать прямо необходимо и пригодится в работе уже завтра. Эти вопросы вполне можно провалить и успешно устроиться на работу. Задача интервьюера — найти «дно» знаний у кандидата, оценить его кругозор, найти области, в которых он «плавает» и наоборот прокачан сильнее необходимого. Для этого диапазон вопросов должен быть достаточно широк. Особенно это ценно при получении первой коммерческой работы.
Честно слово, на предыдущем собеседовании (в другой компании) меня даже спросили, читал ли я Рихтера. После чего, я серьезно взялся за книги, и… Но об этом я, в скором времени, напишу во второй части своей статьи :)
Мне гораздо интереснее другое — многим из тех, кто «читал Рихтера потому что все говорят надо его читать» вообще пригодились эти знания в повседневной работе?
Не скажу вам за тех многих, но, мне кажется, любая (адекватная) прочитанная книга в этой области, по крайней мере, позволяет немного меньше ошибаться, и тратить рабочее время
Да. Инфа там полезная, но подача довольно тяжёлая. Я не думаю, что читать рихтера джунам — это хороший план.
Согласен с вами насчет полезности и тяжелой подачи для начинающих. Но ведь никто не говорит, что его книга должна быть первой прочитанной по .NET :) В моем случае, я сначала читал Троелсена и информацию на сайте metanit
Пункты конечно интересные и занимательные. Но на мой взгляд человеку, только начинающему писать на С#, знать про них не то чтобы надо. Там на мой взгляд другие вещи гораздо важнее.

Да и не для начинающих часть из пунктов интересна только в определённых ситуациях/проектах. И в этих случаях на мой взгляд всё равно надо регулярно поглядывать не меняется ли ситуация с новыми версиями и/или библиотеками
человеку, только начинающему писать на С#

это не джун, это школьник/студент. Максимальная позиция — интерн. Джун — это человек, который пишет код, который потом попадёт в продакшн. И этот код должен быть предсказуемым и поддерживаемым. Когда «джун» случайно увидит в интернетах слово «рефлексия» и начнёт пихать её во все щели в проекте — тогда будут проблемы. Это основы. Это одна несчастная книга, которую можно осилить за полгода и писать код сознательно а не «скомпилилось? ничёси я крутой, коммичу»
Во первых давайте посмотрим на название статьи и увидим что там не ни слова про «джунов»/«миддлов»/«сениоров», а стоит «Заблуждения начинающих C# разработчиков».
А во вторых если взять конкретно те пункты, которые автор описал в своей статье, то они не особо нужны ни джунам, ни даже миддлам. И уж точно они менее важны чем куча других вещей, которые полезно знать людям пишущим на С# и о которых в статье нет ни слова.
Ну я согласен, некоторые пункты из этой статьи не нужны даже мидлам, да и синьёры используют эти познания только на собеседованиях)
Но вот пункты про IDisposable и про количество потоков — прям наболевшее. Кто-то где-то пишет using на общий ресурс, и всё, ищи потом, почему конекшен закрылся внезапно. Ну а с потоками доходило до того что ресурсов на их создание уходило на несколько порядков больше, чем на саму задачу.
Это скорее не заблуждения джунов, а укоренившиеся вредные вопросы с собеседований.
Именно там это любят спрашивать и именно там кочуют неправильные ответы, которые тиражируются на всяких форумах и бложиках. Мне порой казалось, что собеседующие как раз с бложиков и понатягивали эти вопросы.

Как-то раз интервьюер мне спрашивал про разницу между значимыми и ссылочными типами. Я ему рассказал про разницу синтаксиса, про конструкторы, про ссылки на кучу, про способы передачи и упаковку… А он мне, «есть ещё одна разница»… Я чуть потупил, потом говорю про выделение на стеке и в куче. Он радостно подтвердил, что именно это хотел услышать, но я сломал ему систему — рассказал про то, что локальная переменная всегда на стеке (хотя иногда и в регистре сразу), а вот её содержимое может и в куче, если это ссылочный тип. Хуже того, если поле значимого типа часть ссылочного типа, то значение окажется сразу в куче. А если в локальной переменной значимого типа поле ссылочного типа, то значение оного будет снова в куче. А совсем худо от того, что компилятору/оптимизатору может хватить ума весь ссылочный тип уложить на стек, если он коротко живёт.
И чтобы уж совсем его добить, я спросил «Как думаете, а поле типа int в классе претерпевает boxin/unboxing»? (да, это был не первый тупой вопрос на этом собеседовании и я был уже немного злой).

Мораль: всё зло от соцсетей :)
Точнее от того, что джуны не книжки читают, а их краткие пересказы (и это касается не только программирования), не пишут экспериментальный код, а смотрят в ютубе, как это делает кто-то другой :(
А что бы вы советовали почитать? Часто спрашивают, но я в свое время по С# кроме Шилдта и доки ничего не читал.
Что читать — зависит от специализации. Лично моё мнение — в программировании читать книжки по языкам почти бесполезно, если не писать код. Хорошо, когда есть конкретная задача или pet-project.

Если пока ещё нет конкретной цели, но интересен C#, можно начать c официальных туториалов, или с этой подборки.

Я на .net пишу с 2002. Тогда книжек не было — я шерстил msdn, rsdn. Писал, ковырял много чего. Потом избранные главы из разных авторитетных книжек, по мере выявления потребности.
Но я вообще по книжкам учил только Basic (89-й), C по Кернигану и Ритчи (90-й), да C++ по Страуструпу (92-й). Всё остальное — хэлпы, мануалы, доки и метод научного тыка… Да, блин, я английский выучил по докам :)
Да в наше время даже сам Рихтер говорит: «зачем вам книжки писать, если вы их не читаете». Кстати, Рихтер, по сравнеию с Шилдтом пишет ужасно: что на русском, что в оригинале реально снотворное.
Самое лучшее, пока что есть, это статьи и официальная дока. Microsoft очень по этому поводу запарились и привели её в божеский вид.
Стоит читать framework design guidelines.
Рихтер же пишет то, чего в доках нет — про хрупкую внутреннюю кухню.

… реально снотворное.
Самое лучшее, пока что есть, это статьи и официальная дока.

Стоит читать framework design guidelines.

Просытня советов — это разве не скучно?
Нет framework design guidelines это zero bullshit и очень близко к «земле».
Людям скучно читать что-то не про них (Сильмариллион какой-нибудь), а framework design guidelines — это именно про то с чем мы сталкиваемся каждый день.
Ну вот это например: docs.microsoft.com/en-US/dotnet/standard/design-guidelines/choosing-between-class-and-struct
чудо же.
Близость к «земле» — это просто выжимка best practice, с минимумом ответов на вопрос: «Почему?», хотя темы поднимаются те же.
Тоже неплохо (для того, кому нужно завтра проект поддерживать, а технологию он впервые видит и нет времени читать тысячестраничные талмуды), но я бы не сказал, что это даже сейчас может быть достойным заменителем более подробным книгам.

Как мне кажется, не столько времени уходит на само чтение книг, сколько на усвоение информации. Много взаимосвязей, которые бывают неочевидны даже после первого прочтения. Шилдт же проще потому, что у него процент воды к информации выше (и это не плохо, ибо позволяет мозгу выиграть время на усваивание) и материал проще. А Рихтер рискует стать настольной книгой, хотя бы на какое-то время, где даже после первого прочтения придется возвращаться к отдельным главам за вновь появившимися вопросами, когда очередной пласт информации уляжется.
Финализатор. По своей сути является страховкой. Вызывается неявно, в неопределенное время, во время сборки мусора.
Если точнее, то GC только помещает объект в очередь финализации. Из этой очереди объекты забирает отдельный поток финализации, который и вызывает финализатор.
Также пытаться работать с диском из многих потоков — самоубийство. Диск и без того является тормозящим фактором многих программ, которые с ним работают. Если пытаться работать с ним из многих потоков, то надо забыть о быстродействии.


Для SSD проблема становится гораздо менее острой, в большинстве случаев можно не заморачиваться.
А потом в инструкции к программе появится «работает только на ssd»? :)
Также пытаться работать с диском из многих потоков — самоубийство. Диск и без того является тормозящим фактором многих программ, которые с ним работают. Если пытаться работать с ним из многих потоков, то надо забыть о быстродействии.

С одной стороны хорошо, а с другой — не всегда. Можно на выходе получить софт, который тормозит, потому что форматируется дискета.


Реальный пример: однопоточный SMB-сервер. У меня была проблема с умирающим хардом, который мог несколько секунд отвечать на запрос. И пока он отвечал на запрос, работа с другими дисками была невозможна. В итоге подвисали все SMB-соединения.

Если пытаться работать с ним из многих потоков, то надо забыть о быстродействии.

Можно работать с одни диском из одного не UI потока, например. Данная цитата вовсе не означает, что вся программа должна быть однопоточной
Самое большое заблуждение начинающих C# разработчиков, это думать что знание всех нюансов работы GC, памяти, финализатора и прочих «низкоуровневых штук» рантайма автоматом сделает из них «не начинающих» C# разработчиков.
Однако такая имплементация пула объектов — однозначно плохая идея. Как и пытаться логировать, кидать исключения, обращаться к базе и тысячи подобных действий.

Вообще, в финализаторе логировать часто хорошая идея. Потому что вызов финализатора свидетельствует о том, что разработчик забыл Dispose (Естественно, там не забыть GC.SuppressFinalize или вляпать флажок).
Это черевато непредсказуемыми падениями: порядок вызова финализаторов недетерминирован, равно как и порядок убийства объектов. Закрываешь окно приложения, а оно тебе НА! CurrentDomain_UnhandledException.
Чтобы гарантировать, что Dispose() таки вызвали, нужно тесты писать.
Если вызвали Dispose, то финалайзер не должен вызываться. А если его забыли — то явный крэш приложения лучше, чем тихая утечкая ресурсов, которую потом отлавливать по обрывочным сведениям.
Я как-то из-за забытого диспоза при установке приложения клиенту, поехал не на поезд, а в гостиницу. И да, конечно же приложение не говорило, что кто-то забыл диспоз на 78-ой строчке в конкретном файле, и то, что забыт именно диспоз выяснилось потом.

Тем не менее, гарантируется что у обычных объектов финализаторы вызываются раньше чем у наследников CriticalFinalizerObject.


Что означает, в том числе, безопасность обращения к любым SafeHandle в "логирующем" финализаторе.


Ну и между SafeHandle отношения владения можно выстраивать при помощи подсчёта ссылок (DangerousAddRef/DangerousRelease), но тут уже надо внимательно всё проверять.

Угу, только чаще всего для логирования используют что-нибудь вроде log4net, а для логирования пишут что-то типа Logger.Warn(«object not disposed»).
А логгер этот не простой, а «золотой» — использует
abstract class LoggerWrapperImpl {}
в качестве таргета для строчек лога. Это нифига не наследник SafeHandle — вполне может уже прибит в момент работы финализатора.

А зачем он вообще реализует финализатор?

Кто, абстрактный класс? Он его не реализует. А вот реализуют ли его наследники финализатор я знать не могу: это вопрос к тому, кто его будет имплементить, или уже заимплементил. Однако в моём случае он ссылается на PatternLayout, который тоже не SafeHandle.
Господи, вот хоть бы раз за мою долгую карьеру мне как-то пригодилось знание про поколения GC. Кроме как на собеседованиях — вообще ни разу не нужно было. И ещё туча таких же вопросов чисто собеседовательных.
В реальной жизни самое важное это вообще умение декомпозировать задачи, про которое ни на одном собеседовании не спросили и не спросят, потому что оно «не про язык программирования».
Ну, мне поколения часто пригождались, правда больше встрявал в LOH, но и другие ситуации, связанные с «утечками» памяти тоже приходилось расследовать. Но большинству программистов нафиг не надо, да. Вопросы про это бестолковые.
Также пытаться работать с диском из многих потоков — самоубийство. Диск и без того является тормозящим фактором многих программ, которые с ним работают. Если пытаться работать с ним из многих потоков, то надо забыть о быстродействии.

Это может быть два физически разных диска, тогда одним потоком читаем, другим пишем. Это быстрее чем одним потоком оба действия.
Еще бывает и внутреннее распараллеливание потоков записи или чтения (обычно для SSD), тогда последовательная запись данных одним потоком может оказаться еще и медленнее 2-4 потоков.
В третьих у дисков иногда бывает кэш и множество коротких операций чтения/записи работают сильно быстрее в многопоточном режиме, если укладываются в размер кэша.
Если кто в курсе про файловые системы, то у каждого файла есть метаданные (имя, размер, атрибуты, время создания, изменения, доступа, и данные распределения на диске). ОС с целью обеспечения сохранности структур файловой системы все время фиксирует на диск их изменения для каждого файла. То есть один поток вынужден ждать когда ОС зафиксирует очередное изменение на диск, а много потоков могут разделять это время.
Конечно если паттерн обращений на диск не может параллелиться, а зачастую это так, то лучше это делать одним потоком.
Говоря про многопоточность следует упомянуть про работу с почтовыми серверами. Часто, много потоков (в моей практике это было больше 5) это не плохо, а очень плохо. Часть отправок писем могут фейлиться, а установка задержек между отправками сводит преимущества многопоточности на нет. Безусловно, всё зависит и от настроек, и от сервера, и размера писем.
Это не только с почтовыми серверами. Так вообще с любыми сервисами, которые являются «бутылочным горлышком» для задачи. Если туда набросать больше потоков, чем этот сервер может переварить, дело будет плохо, да. Вплоть до того, что могут вообще забанить наш коннект на этом сервисе. )
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации