[Python] Типовые ошибки на собеседовании
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет, сегодня я хотел бы поговорить о некоторых сложностях и заблуждениях, которые встречаются у многих соискателей. Наша компания активно растет, и я часто провожу или участвую в проведении собеседований. В итоге я выделил несколько вопросов, которые многих кандидатов ставят в сложное положение. Давайте вместе рассмотрим их. Я опишу специфические вопросы для Python, но в целом статья подойдет для любого собеседования. Для опытных разработчиков никаких истин тут открыто не будет, но тем, кто только начинает свой путь, будет легче определиться с темами на ближайшие несколько дней.
Отличие процессов от потоков в Linux
Ну вы знаете, такой типичный и, в целом, несложный вопрос, чисто на понимание, без копания в деталях и тонкостях. Конечно, большинство соискателей расскажет, что потоки более легковесны, между ними быстрее переключается контекст, и вообще они живут внутри процесса. И всё это правильно и замечательно, когда мы говорим не о Linux.
Для создания процессов в Linux можно использовать два системных вызова:
- clone(). Это основная функция для создания дочерних процессов. С помощью флагов разработчик указывает, какие структуры родительского процесса должны быть общими с дочерним. Базово используется для создания потоков (имеют общее адресное пространство, файловые дескрипторы, обработчики сигналов).
- fork(). Эта функция используется для создания процессов (которые имеют собственное адресное пространство), но под капотом вызывает clone() с определенным набором флагов.
Я бы обратил внимание на следующее: когда вы сделаете fork() процесса, вы не сразу получите копию памяти родительского процесса. Ваши процессы будут работать с единым экземпляром в памяти. Поэтому, если суммарно у вас должно было случиться переполнение памяти, то всё продолжит работать. Ядро пометит дескрипторы страниц памяти родительского процесса как «только для чтения», а при попытке записи в них (дочерним или родительским процессом) будет вызвано и обработано исключение, которое вызовет создание полной копии. Этот механизм называется Copy-on-Write.
Отличной книгой об устройстве Линукса я считаю «Linux. Системное программирование» за авторством Роберта Лава.
Проблемы с Event Loop
В нашей компании повсеместно распространены асинхронные сервисы и воркеры на Python или Go. Поэтому мы считаем важным общее понимание асинхронности и работы Event Loop. Многие кандидаты уже довольно неплохо отвечают на вопросы о плюсах асинхронного подхода и правильно представляют Event Loop как некий бесконечный цикл, который позволяет понять, не пришло ли определенное событие от операционной системы (например, запись данных в сокет). Но не хватает связующего элемента: как программа получает эту информацию от операционной системы?
Конечно, самое простое, что можно вспомнить — это Select. С его помощью формируется список файловых дескрипторов, за которыми планируется наблюдать. В клиентском коде придется проверять все переданные дескрипторы на наличие событий (и их количество ограничено 1024), что делает его медленным и неудобным.
Ответа про Select более чем достаточно, но если вы вспомните про Poll или Epoll, и расскажете о проблемах, которые они решают, то это будет большим плюсом к вашему ответу. Чтобы не вызывать лишних волнений: код на C и детальную спецификацию у нас не спрашивают, мы говорим лишь о базовом понимании происходящего. Прочитать про различия Select, Poll и Epoll можно в этой статье.
Еще советую посмотреть на тему асинхронности в Python Девида Бизли.
GIL защищает, но не вас
Еще одно распространенное заблуждение заключается в том, что GIL придумали, чтобы защитить разработчиков от проблем с конкурентным доступом к данным. Но это не так. GIL, конечно, не даст вам распараллелить программу с помощью потоков (но не процессов). Проще говоря, GIL — это блокировка, которая должна быть взята перед любым обращением к Python (не так важно. исполняется Python-код или вызовы Python C API). Поэтому GIL защитит внутренние структуры от неконсистентных состояний, но вам, как и в любом другом языке, придется использовать примитивы синхронизации.
Также говорят, что GIL нужен только для корректной работы GC. Для неё он, конечно, нужен, но этим дела не ограничиваются.
С точки зрения исполнения даже самая простая функция будет разбита на несколько шагов:
import dis
def sum_2(a, b):
return a + b
dis.dis(sum_2)
4 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 RETURN_VALUE
С точки зрения процессора каждая из этих операций не является атомарной. Python выполнит очень много процессорных инструкций на каждую строчку байт-кода. При этом нельзя давать другим потокам изменять состояние стека или производить любую другую модификацию памяти, это приведет к Segmentation Fault или некорректному поведению. Поэтому интерпретатор запрашивает глобальную блокировку на выполнение каждой инструкции байт-кода. Однако между отдельными инструкциями контекст может быть изменен, и тут GIL нас никак не спасает. Подробнее про байт-код и как с этим работать можно почитать в документации.
На тему защиты GIL посмотрите простой пример:
import threading
a = 0
def x():
global a
for i in range(100000):
a += 1
threads = []
for j in range(10):
thread = threading.Thread(target=x)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
assert a == 1000000
На моей машине ошибка вылетает стабильно. Если вдруг у вас оно не отработает, то запустите несколько раз или добавьте тредов. При небольшом количестве тредов вы получите плавающую проблему (ошибка то появляется, то не появляется). То есть помимо некорректности данных у таких ситуаций есть еще проблема в виде ее плавающего характера. Также это подводит нас к следующей проблеме: примитивам синхронизации.
И опять не могу не сослаться на Девида Бизли.
Примитивы синхронизации
В целом, примитивы синхронизации — не самый лучший вопрос для Python, но они показывают общее понимание проблемы и то, насколько глубоко вы копали в эту сторону. Тема многопоточности, по крайней мере у нас, спрашивается как бонусная, и будет только плюсом (если вы ответите). Но ничего страшного, если вы с ней еще не сталкивались. Можно сказать, что этот вопрос не привязан к конкретному языку.
Многие начинающие питонисты, как я уже писал выше, надеются на чудотворную силу GIL, поэтому в тему примитивов синхронизации не заглядывают. А зря, это может пригодится при выполнении фоновых операций и задач. Тема примитивов синхронизации большая и хорошо разобранная, в частности, рекомендую почитать об этом в книге «Core Python Applications Programming» автора Wesley J. Chun.
И раз мы уже посмотрели пример, где нам нам не помог GIL в работе с потоками, то рассмотрим самый простой пример, как защититься от подобной проблемы.
import threading
lock = threading.Lock()
a = 0
def x():
global a
lock.acquire()
try:
for i in range(100000):
a += 1
finally:
lock.release()
threads = []
for j in range(10):
thread = threading.Thread(target=x)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
assert a == 1000000
Retry всему голова
Никогда нельзя полагаться на то, что инфраструктура будет всегда стабильно работать. На собеседованиях мы часто просим спроектировать простой микросервис, взаимодействующий с другими (например, по HTTP). Вопрос стабильности сервиса иногда сбивает кандидатов с толку. Я бы хотел обратить внимание на несколько проблем, которые кандидаты не учитывают, когда предлагают делать retry по HTTP.
Первая проблема: сервис может просто не работать продолжительное время. Повторные запросы в реальном времени будут бессмысленны.
Retry, сделанные неаккуратно, могут добить сервис, который начал замедляться под нагрузкой. Меньшее, что ему нужно, это увеличение нагрузки, которая за счет повторных запросов может вырасти в разы. Нам всегда интересно обсудить методы сохранения состояния и осуществления досылки после того, как сервис начнет работать штатно.
Как вариант, можно попытаться сменить протокол с HTTP на что-то с гарантированной доставкой (AMQP и т. д.).
Еще задачу retry может взять на себя service mesh. Подробнее можно почитать в этой статье.
В целом, как я и говорил, никаких сюрпризов тут нет, но эта статья может помочь вам понять, какие темы следует подтянуть. Не только для прохождения собеседований, но и для более глубокого понимания сути происходящих процессов.
===========
Источник:
habr.com
===========
Похожие новости:
- [Python, Машинное обучение, Искусственный интеллект, TensorFlow] Подборка статей о машинном обучении: кейсы, гайды и исследования за ноябрь 2020
- [Open source, *nix] FOSS News №45 – дайджест новостей и других материалов о свободном и открытом ПО за 30 ноября — 6 декабря 2020 года
- [Информационная безопасность] История развития компьютерных вирусов для Unix-подобных систем
- [Системное программирование, Интерфейсы, Разработка под Linux, Программирование микроконтроллеров] Configuring FT4232H using the ftdi_eeprom
- [Python, DevOps] Пушим метрики Prometheus с помощью pushgateway
- [Python, SQL] Немного SQL алхимии
- [Системное программирование, Интерфейсы, Разработка под Linux, Программирование микроконтроллеров] Конфигурируем FT4232H c помощью утилиты ftdi_eeprom
- [Python] Python как компилируемый статически типизированный язык программирования
- [Системное администрирование] /proc/meminfo + gawk = удобный JSON для discovery метрик в zabbix
- [Промышленное программирование, Разработка под Linux, Процессоры] Error: success и что делать по этому поводу
Теги для поиска: #_python, #_sobesedovanie_voprosy (собеседование вопросы), #_python, #_linux, #_blog_kompanii_domklik (
Блог компании ДомКлик
), #_python
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:30
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет, сегодня я хотел бы поговорить о некоторых сложностях и заблуждениях, которые встречаются у многих соискателей. Наша компания активно растет, и я часто провожу или участвую в проведении собеседований. В итоге я выделил несколько вопросов, которые многих кандидатов ставят в сложное положение. Давайте вместе рассмотрим их. Я опишу специфические вопросы для Python, но в целом статья подойдет для любого собеседования. Для опытных разработчиков никаких истин тут открыто не будет, но тем, кто только начинает свой путь, будет легче определиться с темами на ближайшие несколько дней. Отличие процессов от потоков в Linux Ну вы знаете, такой типичный и, в целом, несложный вопрос, чисто на понимание, без копания в деталях и тонкостях. Конечно, большинство соискателей расскажет, что потоки более легковесны, между ними быстрее переключается контекст, и вообще они живут внутри процесса. И всё это правильно и замечательно, когда мы говорим не о Linux. Для создания процессов в Linux можно использовать два системных вызова:
Я бы обратил внимание на следующее: когда вы сделаете fork() процесса, вы не сразу получите копию памяти родительского процесса. Ваши процессы будут работать с единым экземпляром в памяти. Поэтому, если суммарно у вас должно было случиться переполнение памяти, то всё продолжит работать. Ядро пометит дескрипторы страниц памяти родительского процесса как «только для чтения», а при попытке записи в них (дочерним или родительским процессом) будет вызвано и обработано исключение, которое вызовет создание полной копии. Этот механизм называется Copy-on-Write. Отличной книгой об устройстве Линукса я считаю «Linux. Системное программирование» за авторством Роберта Лава. Проблемы с Event Loop В нашей компании повсеместно распространены асинхронные сервисы и воркеры на Python или Go. Поэтому мы считаем важным общее понимание асинхронности и работы Event Loop. Многие кандидаты уже довольно неплохо отвечают на вопросы о плюсах асинхронного подхода и правильно представляют Event Loop как некий бесконечный цикл, который позволяет понять, не пришло ли определенное событие от операционной системы (например, запись данных в сокет). Но не хватает связующего элемента: как программа получает эту информацию от операционной системы? Конечно, самое простое, что можно вспомнить — это Select. С его помощью формируется список файловых дескрипторов, за которыми планируется наблюдать. В клиентском коде придется проверять все переданные дескрипторы на наличие событий (и их количество ограничено 1024), что делает его медленным и неудобным. Ответа про Select более чем достаточно, но если вы вспомните про Poll или Epoll, и расскажете о проблемах, которые они решают, то это будет большим плюсом к вашему ответу. Чтобы не вызывать лишних волнений: код на C и детальную спецификацию у нас не спрашивают, мы говорим лишь о базовом понимании происходящего. Прочитать про различия Select, Poll и Epoll можно в этой статье. Еще советую посмотреть на тему асинхронности в Python Девида Бизли. GIL защищает, но не вас Еще одно распространенное заблуждение заключается в том, что GIL придумали, чтобы защитить разработчиков от проблем с конкурентным доступом к данным. Но это не так. GIL, конечно, не даст вам распараллелить программу с помощью потоков (но не процессов). Проще говоря, GIL — это блокировка, которая должна быть взята перед любым обращением к Python (не так важно. исполняется Python-код или вызовы Python C API). Поэтому GIL защитит внутренние структуры от неконсистентных состояний, но вам, как и в любом другом языке, придется использовать примитивы синхронизации. Также говорят, что GIL нужен только для корректной работы GC. Для неё он, конечно, нужен, но этим дела не ограничиваются. С точки зрения исполнения даже самая простая функция будет разбита на несколько шагов: import dis
def sum_2(a, b): return a + b dis.dis(sum_2) 4 0 LOAD_FAST 0 (a) 2 LOAD_FAST 1 (b) 4 BINARY_ADD 6 RETURN_VALUE С точки зрения процессора каждая из этих операций не является атомарной. Python выполнит очень много процессорных инструкций на каждую строчку байт-кода. При этом нельзя давать другим потокам изменять состояние стека или производить любую другую модификацию памяти, это приведет к Segmentation Fault или некорректному поведению. Поэтому интерпретатор запрашивает глобальную блокировку на выполнение каждой инструкции байт-кода. Однако между отдельными инструкциями контекст может быть изменен, и тут GIL нас никак не спасает. Подробнее про байт-код и как с этим работать можно почитать в документации. На тему защиты GIL посмотрите простой пример: import threading
a = 0 def x(): global a for i in range(100000): a += 1 threads = [] for j in range(10): thread = threading.Thread(target=x) threads.append(thread) thread.start() for thread in threads: thread.join() assert a == 1000000 На моей машине ошибка вылетает стабильно. Если вдруг у вас оно не отработает, то запустите несколько раз или добавьте тредов. При небольшом количестве тредов вы получите плавающую проблему (ошибка то появляется, то не появляется). То есть помимо некорректности данных у таких ситуаций есть еще проблема в виде ее плавающего характера. Также это подводит нас к следующей проблеме: примитивам синхронизации. И опять не могу не сослаться на Девида Бизли. Примитивы синхронизации В целом, примитивы синхронизации — не самый лучший вопрос для Python, но они показывают общее понимание проблемы и то, насколько глубоко вы копали в эту сторону. Тема многопоточности, по крайней мере у нас, спрашивается как бонусная, и будет только плюсом (если вы ответите). Но ничего страшного, если вы с ней еще не сталкивались. Можно сказать, что этот вопрос не привязан к конкретному языку. Многие начинающие питонисты, как я уже писал выше, надеются на чудотворную силу GIL, поэтому в тему примитивов синхронизации не заглядывают. А зря, это может пригодится при выполнении фоновых операций и задач. Тема примитивов синхронизации большая и хорошо разобранная, в частности, рекомендую почитать об этом в книге «Core Python Applications Programming» автора Wesley J. Chun. И раз мы уже посмотрели пример, где нам нам не помог GIL в работе с потоками, то рассмотрим самый простой пример, как защититься от подобной проблемы. import threading
lock = threading.Lock() a = 0 def x(): global a lock.acquire() try: for i in range(100000): a += 1 finally: lock.release() threads = [] for j in range(10): thread = threading.Thread(target=x) threads.append(thread) thread.start() for thread in threads: thread.join() assert a == 1000000 Retry всему голова Никогда нельзя полагаться на то, что инфраструктура будет всегда стабильно работать. На собеседованиях мы часто просим спроектировать простой микросервис, взаимодействующий с другими (например, по HTTP). Вопрос стабильности сервиса иногда сбивает кандидатов с толку. Я бы хотел обратить внимание на несколько проблем, которые кандидаты не учитывают, когда предлагают делать retry по HTTP. Первая проблема: сервис может просто не работать продолжительное время. Повторные запросы в реальном времени будут бессмысленны. Retry, сделанные неаккуратно, могут добить сервис, который начал замедляться под нагрузкой. Меньшее, что ему нужно, это увеличение нагрузки, которая за счет повторных запросов может вырасти в разы. Нам всегда интересно обсудить методы сохранения состояния и осуществления досылки после того, как сервис начнет работать штатно. Как вариант, можно попытаться сменить протокол с HTTP на что-то с гарантированной доставкой (AMQP и т. д.). Еще задачу retry может взять на себя service mesh. Подробнее можно почитать в этой статье. В целом, как я и говорил, никаких сюрпризов тут нет, но эта статья может помочь вам понять, какие темы следует подтянуть. Не только для прохождения собеседований, но и для более глубокого понимания сути происходящих процессов. =========== Источник: habr.com =========== Похожие новости:
Блог компании ДомКлик ), #_python |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:30
Часовой пояс: UTC + 5