Нарезание внутренней резьбы магнитным сверлильным станком
Для нарезания внутренней резьбы магнитным сверлильным станком потребуются такие инструменты, как метчик и резьбонарезной патрон с необходимыми предохранительными головками, которые, как правило, входят в резьбонарезной набор. При отсутствии резьбонарезного набора можно воспользоваться прямым переходником с метчика на Weldon, но в таком случае есть риск поломки инструмента. Также непосредственно перед процессом нарезания, необходимо предварительно просверлить отверстие в детали соответствующего диаметра.
Метчик — это специальный инструмент для нарезания внутренней метрической резьбы в сквозных и глухих отверстиях. Как правило, метчики идут комплектом по две штуки: один черновой — для первичного нарезания резьбы, другой чистовой — для финишной обработки отверстия. Отличие одного метчика от другого в том, что режущая поверхность чернового метчика выражена не так ярко, как чистового.
|
Важно! Метчики должны быть изготовлены из качественного материала! У дешевого китайского инструмента режущая кромка быстро затупится, нарезать резьбу не получится. Метчик просто застрянет в материале, т.к. сработает механизм защиты в предохранительной вставке. С его извлечением проблем будет намного больше, чем с покупкой нового инструмента.
Резьбонарезной патрон и предохранительные вставкиДля нарезания резьбы потребуется резьбонарезной набор. Такой набор включает в себя резьбонарезной патрон с хвостовиком конус Морзе и комплект быстросменных вставок (втулок) для установки метчиков. В патроне имеются системы защиты от перегрузки и осевой компенсации шага резьбы. Вставки имеют механизм регулировки крутящего момента, что позволяет предотвратить поломку инструмента (при увеличении момента метчик останавливается), а так же выполнить качественную резьбу. Вставки имеют удобный механизм быстрой смены метчика обеспечивая при этом его надежную фиксацию. Резьбонарезной патрон применяется в магнитных сверлильных станках как с реверсом вращения шпинделя, так и без него. Разница в том, что магнитный сверлильный станок с реверсом обеспечивает переключение с правого вращения на левое, что позволяет удобно вывести метчик обратно из отверстия, не вынимая его из патрона. | Набор резьбонарезной M12-M24 с предохранительными вставками |
Какого диаметра брать сверло под отверстие, в котором будем нарезать резьбу?
Подходящий диаметр сверла подбираем в зависимости от диаметра резьбы и ее шага. Рекомендуемые значения диаметров отверстий приведены в конце статьи.
Процесс нарезания резьбы
Для глухой резьбы необходимо включить правое вращение и «утопить» метчик в отверстии, после его остановки — переключить направление вращения на левое и метчик плавно выйдет обратно по своей резьбе. Без реверса такой резьбы выполнить невозможно.
Для сквозной резьбы
Рекомендации по процессу нарезания резьбы
Резьбу необходимо выполнять на низких оборотах. При этом нельзя делать резких движений, метчик должен идти очень плавно!
Обязательно следует обильно использовать смазывающую охлаждающую жидкость (концентрат СОЖ) в магнитных сверлильных станках с интегрированной системой ее подачи, или же смазывать извне специальными воском, пастой или пеной.
Диаметры сверл для отверстий под нарезание метрической резьбы по ГОСТ 19257-73 «Отверстия под нарезание метрической резьбы»
Номинальный диаметр резьбы | Шаг резьбы | Диаметр сверла |
1 | 0,2 | 0,8 |
0,25 | 0,75 | |
0,2 | 0,9 | |
1,1 | 0,25 | 0,85 |
0,2 | 1 | |
1,2 | 0,25 | 0,95 |
0,2 | ||
1,4 | 0,3 | 1,1 |
1,6 | 0,2 | 1,4 |
0,35 | 1,25 | |
1,8 | 0,2 | 1,6 |
0,35 | 1,45 | |
2 | 0,25 | 1,75 |
0,4 | 1,6 | |
0,25 | 1,95 | |
2,2 | 0,45 | 1,75 |
2,5 | 0,35 | 2,15 |
0,45 | 2,05 | |
3 | 0,35 | 2,65 |
0,5 | 2,5 | |
0,35 | 3,15 | |
3,5 | 0,6 | 2,9 |
0,5 | 3,5 | |
4 | 0,7 | 3,3 |
0,5 | 4 | |
4,5 | 0,75 | 3,75 |
5 | 0,5 | 4,5 |
0,8 | ||
5,5 | 0,5 | 5 |
0,5 | 5,5 | |
6 | 0,75 | 5,25 |
1 | 5 | |
0,5 | 6,5 | |
7 | 0,75 | 6,25 |
1 | 6 | |
8 | 0,5 | 7,5 |
0,75 | 7,25 | |
1 | 7 | |
1,25 | ||
0,5 | 8,5 | |
9 | 0,75 | 8,25 |
1 | 8 | |
1,25 | 7,8 | |
10 | 0,5 | 9,5 |
0,75 | 9,25 | |
1 | 9 | |
1,25 | 8,8 | |
1,5 | 8,5 | |
11 | 0,5 | 10,5 |
0,75 | ||
1 | 10 | |
1,25 | 9,5 | |
12 | 0,5 | 11,5 |
0,75 | 11,25 | |
1 | 11 | |
1,25 | 10,8 | |
1,5 | 10,5 | |
1,75 | 10,2 | |
14 | 0,5 | 13,5 |
0,75 | 13,25 | |
1 | 13 | |
1,25 | 12,8 | |
1,5 | 12,5 | |
2 | 12 | |
15 | 1 | 14 |
1,5 | 13,5 | |
16 | 0,5 | 15,5 |
0,75 | 15,25 | |
1 | 15 | |
1,5 | 14,5 | |
2 | 14 | |
17 | 1 | 16 |
1,5 | 15,5 | |
18 | 17,5 | |
0,75 | 17,25 | |
1 | 17 | |
1,5 | 16,5 | |
2 | 16 | |
2,5 | 15,5 | |
20 | 0,5 | 19,5 |
0,75 | 19,25 | |
1 | 19 | |
1,5 | 18,5 | |
2 | 18 | |
2,5 | 17,5 |
Номинальный диаметр резьбы | Шаг резьбы | Диаметр сверла |
22 | 0,5 | 21,5 |
0,75 | 21,25 | |
1 | 21 | |
1,5 | 20,5 | |
2 | 20 | |
2,5 | 19,5 | |
24 | 0,75 | 23,25 |
1 | 23 | |
1,5 | 22,5 | |
2 | 22 | |
3 | 21 | |
25 | 1 | 24 |
1,5 | 23,5 | |
2 | 23 | |
26 | 1,5 | 24,5 |
27 | 0,75 | 26,25 |
1 | 26 | |
1,5 | 25,5 | |
2 | 25 | |
3 | 24 | |
28 | 1 | 27 |
1,5 | 26,5 | |
2 | 26 | |
30 | 0,75 | 29,25 |
1 | 29 | |
1,5 | 28,5 | |
2 | 28 | |
3 | 27 | |
3,5 | 26,5 | |
32 | 1,5 | 30,5 |
2 | 30 | |
33 | 0,75 | 32,25 |
1 | 32 | |
1,5 | 31,5 | |
2 | 31 | |
3 | 30 | |
3,5 | 29,5 | |
35 | 1,5 | 33,5 |
36 | 1 | 35 |
1,5 | 34,5 | |
2 | 34 | |
3 | 33 | |
4 | 32 | |
38 | 1,5 | 36,5 |
39 | 1 | 38 |
1,5 | 37,5 | |
2 | 37 | |
3 | 36 | |
4 | 35 | |
40 | 1,5 | 38,5 |
2 | 38 | |
3 | 37 | |
42 | 1 | 41 |
1,5 | 40,5 | |
2 | 40 | |
3 | 39 | |
4 | 38 | |
4,5 | 37,5 | |
45 | 1 | 44 |
1,5 | 43,5 | |
2 | 43 | |
3 | 42 | |
4 | 41 | |
4,5 | 40,5 | |
48 | 1 | 47 |
1,5 | 46,5 | |
2 | 46 | |
3 | 45 | |
4 | 44 | |
5 | 43 | |
50 | 1,5 | 48,5 |
2 | 48 | |
3 | 47 | |
52 | 1 | 51 |
1,5 | 50,5 | |
2 | 50 | |
3 | 49 | |
4 | 48 | |
5 | 47 |
Нарезка резьбы по выгодной стоимости работы в Санкт-Петербурге — ПРОМСТРОЙМЕТАЛЛ
Как технологический процесс нарезка представляет собой нанесение на металлическое изделие специальных впадин (резьбы). Мы оказываем услуги нарезки резьбы металла (гк, хк, оцинкованный, алюминий/нержавейка). Выполняем заказы любой сложности в точные сроки.
Виды резьб, доступные к заказу:
- Дюймовая коническая с углом профиля 60о ГОСТ 6111 (нарезание резьбы для воздушных, водяных, масляных и топливных трубопроводов станков и машин).
- Цилиндрическая трубная ГОСТ 6357, коническая трубная ГОСТ 6211 (резьбовые конические соединения, а также нарезка на конусе наружной резьбы для соединения с цилиндрической внутренней).
- Метрическая, шаг и диаметр определяет ГОСТ 8724, профиль – ГОСТ 9150.
Стоимость нарезки резьбы
Нарезка внутренней резьбы (гк, хк, оцинк, алюминий/нержавейка) | |||
---|---|---|---|
S/М | М3/М6 (1метчик) | М8/М12 (1/2метчика) | М14/М20 (2 метчика) |
S=1-5мм | 20/27 | 30/40,5 | – |
S=5-10мм | 25/34 | 35/47,5 | 45/61 |
S=10-16мм | – | 45/61 | 55/74,5 |
S=16-25мм | – | 55/74,25 | 65/88 |
Наши преимущества
Мы тщательно контролируем производство на всех этапах: на входе сырья, на входе задания, на этапах изготовления на разных переделах, при сдаче заказе. Вы можете мгновенно получать всю информацию о готовности заказа по специальной системе онлайн. 20-летний опыт работы позволил нам отточить все процессы и этапы работы производства, поэтому мы выполняем все заказы точно в срок. При больших количествах одинаковых отверстий на одной детали скидка 20%. Оставляйте заявку или задавайте свои вопросы в форме ниже.
Не удается найти страницу | Autodesk Knowledge Network
(* {{l10n_strings.REQUIRED_FIELD}})
{{l10n_strings.CREATE_NEW_COLLECTION}}*
{{l10n_strings.ADD_COLLECTION_DESCRIPTION}}
{{l10n_strings.COLLECTION_DESCRIPTION}} {{addToCollection.description.length}}/500 {{l10n_strings.TAGS}} {{$item}} {{l10n_strings.PRODUCTS}} {{l10n_strings.DRAG_TEXT}}{{l10n_strings.DRAG_TEXT_HELP}}
{{l10n_strings.LANGUAGE}} {{$select.selected.display}}{{article.content_lang.display}}
{{l10n_strings.AUTHOR}}{{l10n_strings.AUTHOR_TOOLTIP_TEXT}}
{{$select.selected.display}} {{l10n_strings.CREATE_AND_ADD_TO_COLLECTION_MODAL_BUTTON}} {{l10n_strings.CREATE_A_COLLECTION_ERROR}}Нарезка резьбы в деталях, особенности и советы
Внутреннюю резьбу нарезают с помощью метчиков. При нарезании резьбы диаметром до 8 мм вручную пользуются комплектом из трех метчиков: чернового, среднего и чистового, которые на цилиндрической части хвостовика имеют соответственно одну, две и три кольцевые риски.
Большое значение имеет правильный выбор диаметра отверстия. Если диаметр больше, чем следует, то внутренняя резьба не будет иметь полного профиля и получится непрочное соединение. При меньшем диаметре отверстия вход метчика в него затруднен, что ведет к срыву первых ниток резьбы или к заклиниванию и поломке метчика.
Диаметр отверстия под метрическую резьбу можно приближенно определить, умножив размер резьбы на 0,8 (например, для резьбы М2 сверло должно иметь диаметр 1,6 мм, для М3 — 2,4-2,5 мм и т. д. (см. таблицу).
Таблица 1. Диаметры сверл для высверливания отверстий под метрические резьбы.
Сначала резьбу нарезают первым метчиком, затем — вторым. Для скалывания стружки метчик после каждого оборота по часовой стрелке поворачивают на пол-оборота в обратном направлении. Для охлаждения метчика и уменьшения усилия при нарезании резьбы применяют смазки: для алюминия — керосин, для меди — скипидар, для стали — эмульсию.
Бронзу н чугун режут без смазки.
При нарезании глухого отверстия его глубина должна быть больше, чем длина требуемой резьбы, на 3-4 мм. При этом необходимо периодически вывертывать метчик для удаления из отверстия стружки.
Наружную резьбу на винтах, болтах и шпильках нарезают вручную с помощью плашек. Диаметр стержней под наружную метрическую резьбу следует подбирать по таблице.
Таблица 2. Диаметры стержней под метрическую резьбу, выполняемую плашками.
Наружный диаметр резьбы, мм |
Диаметр стержня, мм |
Наружный диаметр резьбы, мм |
Диаметр стержня, мм |
3 | 2,94 | 6 | 5,92 |
3,5 | 3,42 | 7 | 6,9 |
4 | 3,92 | 8 | 7,9 |
4,5 | 4,42 | 9 | 8,9 |
5 | 4,92 | 10 | 9,9 |
Стержень под резьбу должен иметь чистую поверхность; нельзя нарезать резьбу на стержнях, покрытых окалиной нли ржавчиной, так как в этом случае плашки быстро изнашиваются. Перед нарезанием резьбы стержень зажимают в тисках так, чтобы его конец выступал над уровнем губок тисков немного больше длины нарезаемой части, а на самом конце стержня снимают фаску.
В начале нарезания резьбы необходимо следить за тем, чтобы плашка врезалась в стержень без перекоса. При перекосе плашки профиль резьбы искажается, а зубья плашки могут сломаться.
Литература: В. Г. Бастанов. 300 практических советов, 1986г.
Нарезание резьбы — Элмика
Резьба лучше всего производится в инженерных пластиках с использованием инструментов для нарезания резьбы гребенкой или фрезерованием. Наружная резьба на пластмассовых деталях нарезается резцами, плашками, фрезами, внутренняя резьба – метчиками и резцами.
Выбор того или иного метода нарезания резьбы зависит от конструкции и материала обрабатываемой детали, размера резьбы, масштабов производства. Резьбофрезерование является наиболее производительным и экономичным способом образования резьбы в условиях серийного и массового производства. Однако целесообразно фрезеровать резьбы на деталях из жёстких материалов с обязательным охлаждением.
Инструменты
- Резьбу лучше всего наносить резьбовой гребёнкой
- Двузубчатый инструмент позволяет избежать образования заусенцев
- Не рекомендуется использовать нарезные шайбы, так как при удалении шайбы происходит повторная нарезка
Нарезание резьбы резцами
Для нарезания резьбы применяют стержневые, призматические и круглые резцы, профиль режущих кромок которых соответствует профилю резьбы (рис. 5). Стандартные стержневые металлорежущие резцы из быстрорежущей стали и твёрдых сплавов используют при нарезании резьбы в пластмассах с соответствующей переточкой.
Призматические и круглые резьбовые резцы применяются при серийном изготовлении деталей. По сравнению со стержневыми резцами они допускают большее число переточек.
Для сохранения постоянства размеров профиля резьбовых резцов переточка осуществляется только по передней поверхности. При конструировании призматических и круглых резцов угол и размеры профиля резьбы необходимо перечитывать в связи со спецификой установки резца на станке.
При нарезании резьбы резцами должны быть согласованы частота вращения детали и подача суппорта с режущим инструментом: на обороте детали инструмент должен переместиться вдоль оси детали на величину шага резьбы s. Частота вращения детали определяется скоростью резания и может быть подсчитана по формуле: n=1000*V/πd, где d – наружный диаметр резьбы.
При резьбонарезании, как и при точении деталей из пластмасс, наиболее стойкими являются инструменты из твёрдых сплавов группы BК – как более теплопроводные. Алмазные резцы по всем показателям превосходят резцы из других материалов. Быстрорежущие стали, как более дешёвые, применяют при нарезании резьб в деталях из ненаполненных термопластов.
Скорости резания назначают из условия, что температура резания не превышает теплостойкости данного материала. Увеличение скорости резания выше допустимой ухудшает качество нарезаемой резьбы.
При нарезании резьбы в армированных стекловолокном пластиках в качестве смазочно-охлаждающей жидкости можно применять воду. Однако вода снижает прочность резьбы. И детали с высокими требованиями к прочности обрабатывают всухую.
В деталях из термопластов резьбу обычно нарезают быстрорежущими резцами со скоростью резания 10 – 20 м/мин. Глубина резания за один проход не должна быть более 0,1 – 0,3мм* (0,18-0,25мм*).
Рекомендации
- При использовании втулок следует делать припуск на обработку (в зависимости от материала и диаметра, основной показатель 0,1мм)
- Не используйте слишком высокие предварительные настройки для того, чтобы избежать сплющивания резьбы
Нарезание резьбы плашками
Условия нарезания резьбы плашками исключительно тяжёлые (профиль калибрующей части после термообработки не затылуется, а задние углы на боковых режущих кромках близки к 0).В связи с этим точность резьбы низкая (3 – 4 класс), производительность операции невысокая, так как скорости резания обычно не превышают 2 – 3 м/мин. Стандартные металлорежущие плашки нужно перетачивать (ГОСТ 9740-71), уменьшая передний угол до 0о и даже до отрицательных величин (-15 – -20о) и полировать заборный конус и прикромочные участки.
Нарезание резьбы метчиками
Внутренние резьбы в изделиях из пластмасс нарезают специальными метчиками и резцами. Резьбонарезание метчиками связано с определёнными трудностями, обусловленными свойствами обрабатываемого материала: 1) наличием упругих деформаций, вызывающих защемление метчиков при свертывании; 2) высокими абразивными свойствами, способствующими интенсивному износу задних граней метчика; 3) недостатками смазочно-охлаждающих сред.
Нарезание резьбы метчиками малых диаметров проводят в основном вручную, вращая метчик воротком или ручной дрелью. Для серийного и массового производства пластмассовых деталей проектируют специальные метчики, имеющие минимальное число зубьев (n=2-3), широкие полированные или хромированные стружечные канавки, подобранные экспериментально размеры профиля резьбы и геометрические параметры.
Метчики из быстрорежущей стали рекомендуются только для термопластов. Резьбу на деталях из реактопластов нарезают твердосплавными метчиками группы ВК, стойкость которых в 30 – 40 раз выше стойкости быстрорежущих.
Для компенсации упругих деформаций обрабатываемого материала в процессе резьбонарезания и сверления отверстий под резьбу наружный и средний диаметр метчика, а также диаметр сверла увеличивают на 0,05 – 0,1 мм по сравнению с аналогичными размерами метчиков и сверл для обработки металлов.
→ Геометрические параметры метчиков для нарезания резьб в пластмассах назначаются в зависимости от обрабатываемого и инструментального материалов и класса точности резьбы. Подробные параметры обработки Вы можете найти на стр. 15 брошюры «Рекомендации по обработке технических пластмасс» (раздел «Скачать», «Брошюры и каталоги»).
Выбор скорости резания определяется в основном степенью точности нарезаемой резьбы, обрабатываемым материалом и стойкостью инструмента.
Для более качественного нарезания резьбы необходимо пользоваться одним метчиком, так как пластмасса – сравнительно мягкий материал и применение набора метчиков сопряжено с опасностью среза витков резьбы, полученных предыдущим метчиком.
Качество и точность нарезаемой резьбы зависят также от способа крепления метчика на станке – в жёстком или плавающем патроне. Жёсткий патрон не даёт возможности метчику самоустанавливаться по оси отверстия, вследствие чего ось резьбы смещается и появляется местное ослабление профиля резьбы.
Специальный патрон для нарезания сквозных резьб даёт возможность метчику при наличии несоосности последнего с отверстием перемещаться в радиальном направлении так, что ось метчика остаётся параллельной её первоначальному движению.
Приемы нарезания наружной резьбы — Нарезание резьбы
Приемы нарезания наружной резьбы
Категория:
Нарезание резьбы
Приемы нарезания наружной резьбы
Заготовка для нарезания резьбы должна быть чистой и строго цилиндрической (круглой), то есть иметь одинаковый диаметр на всей нарезаемой части стержня.
Рис. 1. Крепление заготовки в тисках, снятие фаски
Рис. 2. Нарезание наружной резьбы
Рис. 3. Дефекты резьбы: а — рваная; б— неполная
Сначала подбирают соответствующую плашку и вставляют ее в плашкодержатель.
На торце зажатой в тиски заготовки снимают напильником фаску, чтобы витки заборной части плашки легче врезались в металл, и смазывают стержень машинным маслом.
На смазанный стержень горизонтально надевают плашку и, слегка нажимая на нее, поворачивают плашкодержатель по часовой стрелке на 1 —1,5 (один-полтора) оборота. Затем примерно на полоборота поворачивают плашку в обратную сторону, чтобы ломались и выпадали образовавшиеся стружки. Повторяют эти операции до конца нарезания резьбы.
Рис. 4. Проверка резьбы калибром
Нажимать на плашкодержатель вниз нужно до тех пор, пока плашка сама не пойдет по резьбе. Нарезают резьбу обычно за один проход.
При нарезании резьба может получиться бракованной: рваной, неполной и косой.
Рваная резьба образуется, когда диаметр нарезаемого стержня значительно больше диаметра резьбы плашки. Нитки резьбы срываются, и резьба прерывается. Неполная резьба получается, если диаметр нарезаемого стержня меньше диаметра резьбы плашки. Косая резьба образуется тогда, когда плашку ставят на стержень с перекосом.
Качество резьбы проверяют резьбовыми калибрами— кольцам.
Запомните!
Перед нарезанием резьбы нужно снять фаску со стержня и смазать его машинным маслом.
Реклама:
Читать далее:
Измерительный слесарный инструмент
Статьи по теме:
Нарезка резьбы — Строительные СНИПы, ГОСТы, сметы, ЕНиР,
ЕНиР
§ Е40-4-4. Нарезка резьбы
Состав работы
1. Закрепление детали в пневматическом прижиме станка с укладкой ее на подставку.
2. Раззенковка конца детали (при нарезке с раззенковкой).
3. Нарезка резьбы.
4. Освобождение детали из прижима станка.
5. Поворачивание и закрепление детали в прижиме станка для нарезки резьбы на втором конце.
6. Раззенковка второго конца детали (при нарезке с раззенковкой).
7. Нарезка резьбы.
8. Освобождение детали из прижима.
9. Регулировка плашек.
10. Проверка качества резьбы контрольной муфтой.
Слесарь 4 разр.
Нормы времени и расценки на 100 резьб
Способ | Вид | Длина | Диаметр труб, мм, до | ||||||
нарезки | резьбы | труб, м, до | 15 | 20 | 25 | 32 | 40 | 50 | |
0,5 | 0,43 0-34 | 0,47 0-37,1 | 0,68 0-53,7 | 0,79 0-62,4 | 1 0-79 | 1,2 0-94,8 | 1 | ||
Короткая | 1 | 0,52 0-41,1 | 0,57 0-45 | 0,73 0-57,7 | 0,88 0-69,5 | 1,1 0-86,9 | 1,4 1-11 | 2 | |
2 | 0,64 0-50,6 | 0,67 0-52,9 | 0,78 0-61,6 | 0,97 0-76,6 | 1,2 0-94,8 | 1,6 1-26 | 3 | ||
С раззенковкой | 3 | 0,76 0-60 | 0,77 0-60,8 | 0,83 0-65,6 | 1,1 0-86,9 | 1,3 1-03 | 1,8 1-42 | 4 | |
0,5 | 0,77 0-60,8 | 0,84 0-66,4 | 1,1 0-86,9 | 1,4 1-11 | 1,7 1-34 | 2,1 1-66 | 5 | ||
Длинная | 1 | 0,87 0-68,7 | 0,97 0-76,6 | 1,2 0-94,8 | 1,5 1-19 | 1,8 1-42 | 2,3 1-82 | 6 | |
2 | 0,97 0-76,6 | 1,1 0-86,9 | 1,3 1-03 | 1,6 1-26 | 1,9 1-50 | 2,5 1-98 | 7 | ||
3 | 1,1 0-86,9 | 1,2 0-94,8 | 1,4 1-11 | 1,7 1-34 | 2 21-58 | 2,7 2-13 | 8 | ||
Без раззенковки | Короткая | 2 | 0,52 0-41,1 | 0,56 0-44,2 | 0,6 0-47,4 | 0,64 0-50,6 | 1,2 0-94,8 | 1,5 1-19 | 9 |
Длинная | 0,73 0-57,7 | 0,81 0-64 | 0,89 0-70,3 | 0,96 0-75,8 | 1,6 1-26 | 2 1-58 | 10 | ||
а | б | в | г | д | e | № |
Нити для бровей: 8 вещей, которые нужно знать перед тем, как пробовать
Я впервые попробовала нарезать брови нитью только потому, что других вариантов не было. Я был в офисе Glamour для фотосессии (моих настоящих и очень неопрятных бровей, не меньше), и хотя в прошлом я натирал и выщипывал брови воском, мой нынешний M.O. вообще ничего не делал. Тем не менее, мои брови нужно было отполировать (читай: полностью переделать), поэтому я подал заявку. Либо это было так, либо в Интернете распространялась моя фотография, на которой я напоминал женскую версию Питера Галлахера, также известного как папа-красавчик Сэнди Коэн из The O.C. (на случай, если вам нужно освежить память).
Я был в ужасе. Нить для бровей всегда казалась самым садистским вариантом удаления волос, хотя это сложная гонка, чтобы выиграть, если вы добавляете выщипывание отдельных волосков и сдирание воска с кожи. Техника, которая на протяжении веков была основным способом удаления волос в Азии, заключается в наматывании скрученной хлопковой нити на волосы, которая бесцеремонно вырывает каждый фолликул прямо из поры. Я знаю, я знаю: это не похоже на приятное занятие, и у меня заведомо низкий болевой порог.Но как только я действительно почувствовал это на себе — и увидел результаты в зеркале — я понял, что никогда не вернусь. Веселье? Не совсем. Но оно того стоит? Абсолютно.
Это не так уж больно
Это самый большой фактор, который удерживает многих от многопоточности, говорит Сабах Фероз, эксперт по бровям из BlinkBrowBar в Нью-Йорке. В этом нет необходимости: «Наши клиенты склонны описывать наложение нитей как необычное, но не болезненное ощущение», — говорит она. Если вы очень нервничаете, вы можете попросить специалиста по нарезанию ниток сначала попробовать его на персиковом пухе на тыльной стороне ладони.По словам Фероза, когда люди испытывают это таким образом, они обычно перестают поправлять брови.
Он придает вашим бровям точный вид
Нить для бровей — это, по сути, точность. «Из фолликула можно вырвать даже самые крошечные волоски», — говорит Фероз. Поскольку он забирает каждый волосок, не остается ни одного недоразвитого блуда. Кроме того, заправка нитей позволяет полностью контролировать, какие волоски удаляются, а какие нет. «Вы можете получить действительно точную форму», — говорит Марко Очоа, знаменитый специалист по продвижению бровей и владелец EcoBrow Studio в Беверли-Хиллз.«Вы можете продеть по одному волосу за раз или по прямой, чтобы брови были четко очерчены». (Для еще большей четкости соедините его с микроблейдингом или микрозатенением).
Техническое обслуживание намного проще
В отличие от выщипывания, которое не всегда может захватить более короткие, частично выросшие фолликулы, нарезание нитей может удалить все ваши нежелательные волосы сразу, — объясняет Амбрин Шейх, владелица Wink Brow Bar в Нью-Йорке и женщина, которая присматривала за ней. моя собственная форма бровей. Из-за этого они все вырастут одновременно, поэтому вам не нужно вынимать пинцет или останавливаться для подкраски каждую неделю.В общем, «люди любят убирать дома выщипыванием щипцов после двух недель использования ниток», — говорит Очоа. «Я рекомендую своим клиентам приходить через четыре-пять недель, чтобы изменить форму бровей».
Это быстрый процесс
Большинство профессионалов в области бровей смогут помочь вам быстро войти и выйти. Я ожидал, что буду сидеть там вечно, пока они рвали и выдергивали каждый волосок на лбу, но все это занимает максимум 10 минут. А если ваши брови уже в относительно хорошем состоянии? Ожидайте, что это займет еще меньше времени.
Это примерно столько же, сколько и восковая эпиляция
Хорошие новости, если вы думаете о переходе с восковой эпиляции на брови: в зависимости от того, куда вы идете, они обычно находятся в одном ценовом диапазоне. Например, в Wink Brow Bar классическое 10-минутное моделирование стоит 27 долларов, как и обычный воск для бровей. Вы также должны ожидать, что расходы будут варьироваться в зависимости от местоположения и специализации салона, но планируйте платить от 10 до 40 долларов плюс чаевые.
Это самая щадящая форма удаления волос …
Поскольку дергается только волосы, а кожа не является случайной травмой, вероятность раздражения очень мала.«Кожу не растягивают и не натягивают», — говорит Фероз. Нарезание нитей также легче на чувствительной и склонной к акне коже, поэтому, если после депиляции ваши брови остаются сырыми или красными на более длительный период, чем вам удобно, наложение ниток для бровей может оказаться менее интенсивным.
… Но это все еще не спа-день
Хотя — это самая щадящая форма удаления волос, вы все равно удаляете волосы. Поскольку хлопковая нить трется о кожу, она может вызвать небольшое покраснение и незначительное раздражение на очень чувствительной коже.Если это похоже на вас, просто завершите службу охлаждающим гелем. «Розовая вода, алоэ вера или гель чайного дерева помогают успокоить кожу, успокоить кожу и быстро удалить любые покраснения», — говорит Фероз. Некоторые салоны резьбонарезания предлагают постобработку бесплатно. Если нет, то мы большие поклонники спрея для лица Марио Бадеску, который содержит успокаивающее алоэ, травы и розовую воду.
Сохранить только для волос на лице
«Нить — идеальное временное решение для удаления любых волос на лице, например, бровей», — говорит Фероз.Однако из-за своей точности это не лучший вариант для любого другого участка вашего тела — отчасти потому, что это займет целую вечность, а отчасти потому, что час или больше, чтобы вырвать волосы, вероятно, не то, что кто-то захочет делать. «Вощение или лазер намного быстрее и определенно предпочтительнее для больших участков», — говорит она. Хотите узнать больше об этих вариантах? У нас есть все, что вам нужно знать.
threading — Управление параллельными потоками
Цель: | Создает на основе модуля потоков, чтобы упростить управление несколькими потоками выполнения. |
---|---|
Доступен в версиях: | 1.5.2 и выше |
Модуль потоковой передачи основан на низкоуровневых функциях thread, чтобы сделать работу с потоками еще проще и многое другое питонический . Использование потоков позволяет программе выполнять несколько операций одновременно в одном и том же пространстве процесса.
Объекты резьбы
Самый простой способ использовать поток — создать его экземпляр с помощью целевую функцию и вызовите start (), чтобы она начала работать.
импортная резьба def worker (): "" "функция рабочего потока" "" print 'Рабочий' возвращение thread = [] для i в диапазоне (5): t = threading.Thread (цель = рабочий) threads.append (t) t.start ()
Результатом будет пять строк с надписью «Worker» на каждой:
$ Python threading_simple.py Рабочий Рабочий Рабочий Рабочий Рабочий
Полезно иметь возможность создавать поток и передавать ему аргументы, чтобы сообщить это какая работа делать. В этом примере передается число, которое поток затем печатает.
импортная резьба def worker (число): "" "функция рабочего потока" "" print 'Worker:% s'% num возвращение thread = [] для i в диапазоне (5): t = threading.Thread (цель = рабочий, args = (i,)) threads.append (t) t.start ()
Целочисленный аргумент теперь включен в сообщение, выводимое каждым нить:
$ python -u threading_simpleargs.py Рабочий: 0 Рабочий: 1 Рабочий: 2 Рабочий: 3 Рабочий: 4
Определение текущего потока
Использование аргументов для идентификации или присвоения имени потоку затруднительно, и ненужный.Каждый экземпляр потока имеет имя со значением по умолчанию значение, которое можно изменить по мере создания потока. Именование потоков полезно в серверных процессах с обработкой нескольких потоков службы разные операции.
импортная резьба время импорта def worker (): print threading.currentThread (). getName (), 'Запуск' время сна (2) print threading.currentThread (). getName (), 'Выход' def my_service (): print threading.currentThread (). getName (), 'Запуск' время сна (3) печать заправки.currentThread (). getName (), 'Выход' t = threading.Thread (name = 'my_service', target = my_service) w = threading.Thread (имя = 'работник', цель = работник) w2 = threading.Thread (target = worker) # использовать имя по умолчанию w.start () w2.start () t.start ()
Выходные данные отладки включают имя текущего потока на каждом линия. Строки с «Thread-1» в столбце имени потока соответствуют безымянному потоку w2.
$ python -u имя_потокаs.py рабочий поток-1 запускается my_service Запуск Запуск Thread-1worker Exiting Выход my_service Выход
Большинство программ не используют print для отладки.В модуль ведения журнала поддерживает встраивание имени потока в каждый журнал сообщение, используя код средства форматирования% (threadName) s. Включая поток имена в сообщениях журнала упрощают отслеживание этих сообщений до их источник.
импорт журнала импорт потоковой передачи время импорта logging.basicConfig (уровень = logging.DEBUG, format = '[% (levelname) s] (% (threadName) -10s)% (message) s', ) def worker (): logging.debug ("Запускается") время сна (2) logging.debug ("Выход") def my_service (): протоколирование.отладка ("Запускается") время сна (3) logging.debug ("Выход") t = threading.Thread (name = 'my_service', target = my_service) w = threading.Thread (имя = 'работник', цель = работник) w2 = threading.Thread (target = worker) # использовать имя по умолчанию w.start () w2.start () t.start ()Ведение журнала
также является потокобезопасным, поэтому сообщения из разных потоков на выходе сохраняются отдельно.
$ python threading_names_log.py [DEBUG] (рабочий) Запускается [DEBUG] (Thread-1) Запускается [DEBUG] (my_service) Запускается [DEBUG] (рабочий) Выходит [DEBUG] (Thread-1) Выход [DEBUG] (my_service) Выход
Демон vs.Не-демонические потоки
До этого момента программы примеров неявно ожидали выхода пока все потоки не завершат свою работу. Иногда появляются программы поток как демон , который работает без блокировки основной программы от выхода. Использование потоков демона полезно для сервисов, где есть может быть нелегким способом прервать нить или позволить поток умирает посреди своей работы не теряет и не повреждает данные (например, поток, который генерирует «сердцебиение» для службы инструмент мониторинга).Чтобы пометить поток как демон, назовите его setDaemon () с логическим аргументом. По умолчанию потоки не должны быть демонами, поэтому передача True включает режим демона.
импортная резьба время импорта импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def daemon (): logging.debug ("Запускается") время сна (2) logging.debug ("Выход") d = threading.Thread (имя = 'демон', цель = демон) d.setDaemon (Истина) def non_daemon (): logging.debug ("Запускается") logging.debug ("Выход") t = threading.Thread (name = 'non-daemon', target = non_daemon) d.start () t.start ()
Обратите внимание, что выходные данные не включают сообщение «Выход» из поток демона, поскольку все потоки не-демона (включая основной поток) выйти до того, как поток демона проснется после двухсекундного спать.
$ python threading_daemon.py (демон) Запуск (не-демон) Запуск (не демон) Выход
Чтобы дождаться, пока поток демона завершит свою работу, используйте join () метод.
импортная резьба время импорта импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def daemon (): logging.debug ("Запускается") время сна (2) logging.debug ("Выход") d = threading.Thread (имя = 'демон', цель = демон) d.setDaemon (Истина) def non_daemon (): logging.debug ("Запускается") logging.debug ("Выход") t = threading.Thread (name = 'non-daemon', target = non_daemon) d.start () t.start () d.присоединиться() t.join ()
Ожидание завершения потока демона с помощью join () означает, что имеет шанс выдать сообщение «Выход».
$ Python threading_daemon_join.py (демон) Запуск (не-демон) Запуск (не демон) Выход (демон) Выход
По умолчанию join () блокируется на неопределенный срок. Также возможно передать аргумент тайм-аута (число с плавающей запятой, представляющее количество секунд до подождите, пока поток станет неактивным). Если нить не завершено в течение периода ожидания, join () все равно возвращается.
импортная резьба время импорта импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def daemon (): logging.debug ("Запускается") время сна (2) logging.debug ("Выход") d = threading.Thread (имя = 'демон', цель = демон) d.setDaemon (Истина) def non_daemon (): logging.debug ("Запускается") logging.debug ("Выход") t = threading.Thread (name = 'non-daemon', target = non_daemon) d.start () t.start () d.присоединиться (1) напечатать 'd.isAlive ()', d.isAlive () t.join ()
Поскольку истекший тайм-аут меньше, чем время, в течение которого демон поток спит, поток все еще «жив» после join () возвращается.
$ Python threading_daemon_join_timeout.py (демон) Запуск (не-демон) Запуск (не демон) Выход d.isAlive () Истина
Перечисление всех потоков
Нет необходимости сохранять явный дескриптор для всего демона потоки, чтобы убедиться, что они завершились перед выходом из основного процесс.enumerate () возвращает список активных потоков экземпляры. Список включает текущий поток, и с момента присоединения к текущий поток не разрешен (это приводит к тупиковой ситуации), он необходимо пропустить.
случайный импорт импорт потоковой передачи время импорта импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def worker (): "" "функция рабочего потока" "" t = threading.currentThread () пауза = случайный.рандинт (1,5) logging.debug ('спящий% s', пауза) time.sleep (пауза) logging.debug ('окончание') возвращение для i в диапазоне (3): t = threading.Thread (цель = рабочий) t.setDaemon (Истина) t.start () main_thread = threading.currentThread () для t в threading.enumerate (): если t - main_thread: Продолжить logging.debug ('присоединение к% s', t.getName ()) t.join ()
Поскольку рабочий спит случайное время, вывод от этой программы может отличаться. Должно получиться примерно так:
$ python threading_enumerate.ру (Thread-1) спящий 3 (Thread-2) спящий 2 (Thread-3) спящая 5 (MainThread) присоединяется к Thread-1 (Тема-2) окончание (Тема-1) окончание (MainThread) присоединение к Thread-3 (Поток-3) окончание (MainThread) присоединение к Thread-2
Нить подкласса
При запуске поток выполняет базовую инициализацию, а затем вызывает свой метод run (), который вызывает переданную целевую функцию конструктору. Чтобы создать подкласс Thread, переопределите run (), чтобы делать все, что необходимо.
импортная резьба импорт журнала протоколирование.basicConfig (level = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) класс MyThread (threading.Thread): def run (self): logging.debug ('работает') возвращение для i в диапазоне (5): t = MyThread () t.start ()
Возвращаемое значение run () игнорируется.
$ Python threading_subclass.py (Поток-1) работает (Поток-2) работает (Поток-3) работает (Поток-4) работает (Поток-5) работает
Поскольку значения args и kwargs переданы потоку конструктор сохраняются в частных переменных, их нелегко доступ из подкласса.Чтобы передать аргументы настраиваемому типу потока, переопределите конструктор, чтобы сохранить значения в атрибуте экземпляра что можно увидеть в подклассе.
импортная резьба импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) класс MyThreadWithArgs (threading.Thread): def __init __ (self, group = None, target = None, name = None, args = (), kwargs = None, verbose = None): threading.Thread.__init __ (я, группа = группа, цель = цель, имя = имя, verbose = подробный) self.args = args self.kwargs = kwargs возвращение def run (self): logging.debug ('работает с% s и% s', self.args, self.kwargs) возвращение для i в диапазоне (5): t = MyThreadWithArgs (args = (i,), kwargs = {'a': 'A', 'b': 'B'}) t.start ()
MyThreadWithArgs использует тот же API, что и Thread, но другой класс может легко изменить метод конструктора, чтобы он занимал больше или различные аргументы, более непосредственно связанные с целью thread, как и в любом другом классе.
$ Python threading_subclass_args.py (Thread-1) работает с (0,) и {'a': 'A', 'b': 'B'} (Thread-2) работает с (1,) и {'a': 'A', 'b': 'B'} (Thread-3) работает с (2,) и {'a': 'A', 'b': 'B'} (Thread-4) работает с (3,) и {'a': 'A', 'b': 'B'} (Thread-5) работает с (4,) и {'a': 'A', 'b': 'B'}
Таймер потоков
Один из примеров причины для создания подкласса Thread предоставляется Таймер, также включенный в многопоточность. Таймер начинает свою работу с задержкой и может быть отменен в любой момент в течение этот период времени задержки.
импортная резьба время импорта импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def delayed (): logging.debug ('рабочий работает') возвращение t1 = threading.Timer (3, с задержкой) t1.setName ('t1') t2 = threading.Timer (3, с задержкой) t2.setName ('t2') logging.debug ('таймеры запуска') t1.start () t2.start () logging.debug ('ожидание перед отменой% s', t2.getName ()) время сна (2) logging.debug ('отмена% s', t2.getName ()) t2.cancel () logging.debug ('готово')
Обратите внимание, что второй таймер никогда не запускается, и появляется первый таймер. для запуска после завершения остальной части основной программы. Поскольку это не поток демона, он присоединяется неявно, когда основной поток завершен.
$ python threading_timer.py (MainThread) стартовые таймеры (MainThread) ожидание перед отменой t2 (MainThread) отмена t2 (MainThread) выполнено (t1) рабочий работает
Передача сигналов между потоками
Хотя смысл использования нескольких потоков состоит в том, чтобы вращать отдельные операции отключены для одновременного выполнения, бывают случаи, когда важно иметь возможность синхронизировать операции в двух или более потоки.Простой способ связи между потоками — использование Объекты событий. Событие управляет внутренним флагом что вызывающие могут установить () или очистить (). Другой потоки могут wait () для установки флага (), эффективно блокирует прогресс до тех пор, пока не будет разрешено продолжить.
импорт журнала импорт потоковой передачи время импорта logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def wait_for_event (e): "" "Прежде чем что-либо делать, дождитесь установки события" "" протоколирование.отладка ('ожидание_for_event запускается') event_is_set = e.wait () logging.debug ('набор событий:% s', event_is_set) def wait_for_event_timeout (e, t): "" "Подождите t секунд, а затем тайм-аут" "" пока не e.isSet (): logging.debug ('запуск wait_for_event_timeout') event_is_set = e.wait (t) logging.debug ('набор событий:% s', event_is_set) если event_is_set: logging.debug ('событие обработки') еще: logging.debug ('выполняю другую работу') е = заправка.Мероприятие() t1 = threading.Thread (name = 'блок', target = wait_for_event, args = (e,)) t1.start () t2 = threading.Thread (name = 'неблокируемый', target = wait_for_event_timeout, args = (e, 2)) t2.start () logging.debug ('Ожидание перед вызовом Event.set ()') время сна (3) e.set () logging.debug ('Событие установлено')
Метод wait () принимает аргумент, представляющий количество секунд, чтобы дождаться события до истечения времени ожидания.Возвращает логическое значение указывает, установлено ли событие, чтобы вызывающий абонент знал, почему wait () вернулся. Можно использовать метод isSet () отдельно по событию, не опасаясь блокировки.
В этом примере wait_for_event_timeout () проверяет событие статус без блокировки на неопределенный срок. Wait_for_event () блокирует вызов wait (), который не возвращается до тех пор, пока статус события меняется.
$ python threading_event.py (блок) запуск wait_for_event (неблокируемый) запуск wait_for_event_timeout (MainThread) Ожидание перед вызовом Event.установленный() (неблокирующий) набор событий: Ложь (неблокирующий) выполнение другой работы (неблокируемый) запуск wait_for_event_timeout (MainThread) Событие установлено (блок) набор событий: True (неблокирующий) набор событий: True (неблокирующее) событие обработки
Контроль доступа к ресурсам
Помимо синхронизации операций потоков, он также важно иметь возможность контролировать доступ к общим ресурсам для предотвращения повреждение или пропущенные данные. Встроенные структуры данных Python (списки, словари и др.) поточно-ориентированы как побочный эффект наличия атомарных байт-коды для управления ими (GIL не выпускается в середина обновления). Другие структуры данных, реализованные на Python, или более простые типы, такие как целые числа и числа с плавающей запятой, не имеют такой защиты. К предохраняться от одновременного доступа к объекту, использовать Замок объект.
импорт журнала случайный импорт импорт потоковой передачи время импорта logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) класс Counter (объект): def __init __ (self, start = 0): себя.lock = threading.Lock () self.value = начало def приращение (self): logging.debug ('Ожидание блокировки') self.lock.acquire () пытаться: logging.debug ('Полученная блокировка') self.value = self.value + 1 наконец: self.lock.release () def worker (c): для i в диапазоне (2): пауза = random.random () logging.debug ('Sleeping% 0.02f', пауза) time.sleep (пауза) c.increment () logging.debug ('Готово') counter = Счетчик () для i в диапазоне (2): t = заправка.Тема (target = worker, args = (counter,)) t.start () logging.debug ('Ожидание рабочих потоков') main_thread = threading.currentThread () для t в threading.enumerate (): если t не main_thread: t.join () logging.debug ('Счетчик:% d', counter.value)
В этом примере функция worker () увеличивает Экземпляр счетчика, который управляет блокировкой для предотвращения два потока от одновременного изменения своего внутреннего состояния. Если Блокировка не использовалась, есть вероятность пропустить изменение к атрибуту значения.
$ python threading_lock.py (Тема-1) Спящая 0,47 (Поток-2) Спящий 0,65 (MainThread) Ожидание рабочих потоков (Thread-1) Ожидание блокировки (Thread-1) Получил блокировку (Тема-1) Сон 0.90 (Thread-2) Ожидание блокировки (Thread-2) Получил блокировку (Thread-2) Спящий 0.11 (Thread-2) Ожидание блокировки (Thread-2) Получил блокировку (Тема-2) Готово (Thread-1) Ожидание блокировки (Thread-1) Получил блокировку (Тема-1) Готово (MainThread) Счетчик: 4
Чтобы узнать, получил ли другой поток блокировку без удерживая текущий поток, передайте False для блокирующего аргумента приобрести().В следующем примере worker () пытается захватывает блокировку три раза и подсчитывает, сколько попыток должен сделать это. Между тем, lock_holder () циклически между удержанием и снятием замка с короткими паузами в каждом состояние, используемое для имитации нагрузки.
импорт журнала импорт потоковой передачи время импорта logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def lock_holder (замок): протоколирование.отладка ("Запускается") в то время как True: lock.acquire () пытаться: logging.debug ('Удержание') time.sleep (0,5) наконец: logging.debug ('Не удерживается') lock.release () time.sleep (0,5) возвращение def worker (блокировка): logging.debug ("Запускается") num_tries = 0 num_acquires = 0 а num_acquires <3: time.sleep (0,5) logging.debug ('Пытаюсь получить') have_it = lock.acquire (0) пытаться: num_tries + = 1 если have_it: протоколирование.отладка ("Итерация% d: получено", число_попыток) num_acquires + = 1 еще: logging.debug ('Итерация% d: не получена', num_tries) наконец: если have_it: lock.release () logging.debug ('Выполнено после% d итераций', num_tries) lock = threading.Lock () держатель = threading.Thread (target = lock_holder, args = (lock,), name = 'LockHolder') Holder.setDaemon (Истина) Holder.start () worker = threading.Thread (target = worker, args = (lock,), name = 'Рабочий') рабочий.Начните()
Для получения блокировки worker () требуется более трех итераций. три разных раза.
$ Python threading_lock_noblock.py (LockHolder) Запуск (LockHolder) Удерживая (Рабочий) Запуск (LockHolder) Не удерживается (Рабочий) Пытается приобрести (Рабочий) Итерация 1: Получено (Рабочий) Пытается приобрести (LockHolder) Удерживание (Рабочий) Итерация 2: Не получено (LockHolder) Не удерживается (Рабочий) Пытается приобрести (Рабочий) Итерация 3: Получено (LockHolder) Удерживая (Рабочий) Пытается приобрести (Рабочий) Итерация 4: Не получено (LockHolder) Не удерживается (Рабочий) Пытается приобрести (Рабочий) Итерация 5: Получено (Рабочий) Сделано после 5 итераций
Замки для повторного входа
Обычных объектов блокировки нельзя получить более одного раза, даже той же веткой.Это может вызвать нежелательные побочные эффекты, если lock используется более чем одной функцией в одной цепочке вызовов.
импортная резьба lock = threading.Lock () print 'Первая попытка:', lock.acquire () print 'Вторая попытка:', lock.acquire (0)
В этом случае, поскольку обе функции используют одну и ту же глобальную блокировку, и один вызывает другой, второе получение не удается и заблокирован с использованием аргументов по умолчанию для Acquire ().
$ Python threading_lock_reacquire.py Первая попытка: верно Вторая попытка: ложь
В ситуации, когда отдельный код из одного и того же потока должен «Повторно захватите» блокировку, вместо этого используйте RLock.
импортная резьба lock = threading.RLock () print 'Первая попытка:', lock.acquire () print 'Вторая попытка:', lock.acquire (0)
Единственное изменение в коде из предыдущего примера - замена RLock для блокировки.
$ python threading_rlock.py Первая попытка: верно Вторая попытка: 1
Блокировки как менеджеры контекста
Блокировки реализуют API диспетчера контекста и совместимы с с заявлением . Использование с устраняет необходимость явно получить и снять блокировку.
импортная резьба импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def worker_with (блокировка): с замком: logging.debug ('Блокировка получена через с помощью') def worker_no_with (блокировка): lock.acquire () пытаться: logging.debug ('Блокировка получена напрямую') наконец: lock.release () lock = threading.Lock () w = threading.Thread (target = worker_with, args = (блокировка,)) nw = резьба.Поток (target = worker_no_with, args = (lock,)) w.start () nw.start ()
Две функции worker_with () и worker_no_with () управлять замком аналогичным образом.
$ питон threading_lock_with.py (Thread-1) Блокировка получена через (Thread-2) Замок получен напрямую
Синхронизация потоков
Помимо использования событий, есть еще один способ синхронизации. потоков осуществляется с помощью объекта Condition. Поскольку Условие использует блокировку, его можно привязать к общему ресурс.Это позволяет потокам ждать обновления ресурса. В этом примере потоки consumer () wait () для Перед продолжением необходимо установить условие. Продюсер () поток отвечает за установку условия и уведомление другие темы, которые они могут продолжить.
импорт журнала импорт потоковой передачи время импорта logging.basicConfig (уровень = logging.DEBUG, format = '% (asctime) s (% (threadName) -2s)% (message) s', ) def потребитель (cond): "" "дождитесь выполнения условия и воспользуйтесь ресурсом" "" протоколирование.debug ('Запуск потребительского потока') t = threading.currentThread () с конд: cond.wait () logging.debug ('Ресурс доступен потребителю') производитель def (cond): "" "настроить ресурс, который будет использоваться потребителем" "" logging.debug ('Запуск потока производителя') с конд: logging.debug ('Делаем ресурс доступным') cond.notifyAll () condition = threading.Condition () c1 = threading.Thread (имя = 'c1', цель = потребитель, args = (условие,)) c2 = threading.Thread (имя = 'c2', цель = потребитель, args = (условие,)) p = резьба.Тема (имя = 'p', цель = производитель, args = (условие,)) c1.start () время сна (2) c2.start () время сна (2) p.start ()
Потоки используют с для получения блокировки, связанной с состояние. Используя Acquile () и Метод release () также работает явно.
$ Python threading_condition.py 2013-02-21 06: 37: 49,549 (c1) Запуск потока потребителя 2013-02-21 06: 37: 51,550 (c2) Запуск потока потребителя 2013-02-21 06: 37: 53,551 (p) Начало темы производителя 2013-02-21 06: 37: 53,552 (p) Обеспечение доступности ресурсов 2013-02-21 06: 37: 53,552 (c2) Ресурс доступен потребителю 2013-02-21 06: 37: 53,553 (c1) Ресурс доступен потребителю
Ограничение одновременного доступа к ресурсам
Иногда полезно разрешить более чем одному рабочему доступу к ресурс за раз, но при этом общее количество ограничено.Для Например, пул соединений может поддерживать фиксированное количество одновременных подключений, или сетевое приложение может поддерживать фиксированное количество одновременных загрузок. Семафор - это односторонний для управления этими связями.
импорт журнала случайный импорт импорт потоковой передачи время импорта logging.basicConfig (уровень = logging.DEBUG, format = '% (asctime) s (% (threadName) -2s)% (message) s', ) класс ActivePool (объект): def __init __ (сам): супер (ActivePool, сам).__в этом__() self.active = [] self.lock = threading.Lock () def makeActive (я, имя): с self.lock: self.active.append (имя) logging.debug ('Выполняется:% s', self.active) def makeInactive (я, имя): с self.lock: self.active.remove (имя) logging.debug ('Выполняется:% s', self.active) def worker (s, pool): logging.debug ('Ожидание присоединения к пулу') с s: имя = threading.currentThread (). getName () бассейн.makeActive (имя) time.sleep (0,1) pool.makeInactive (имя) pool = ActivePool () s = резьба. сэмафора (2) для i в диапазоне (4): t = threading.Thread (target = worker, name = str (i), args = (s, пул)) t.start ()
В этом примере класс ActivePool просто служит удобный способ отслеживать, какие потоки могут запускаться при заданном момент. Реальный пул ресурсов выделит соединение или другой значение для нового активного потока и вернуть значение, когда резьба сделана.Здесь он просто используется для хранения имен активных потоков, чтобы показать, что одновременно работают только пять.
$ питон threading_semaphore.py 2013-02-21 06: 37: 53,629 (0) Ожидание присоединения к бассейну 2013-02-21 06: 37: 53,629 (1) Ожидание присоединения к бассейну 2013-02-21 06: 37: 53,629 (0) Выполняется: ['0'] 2013-02-21 06: 37: 53,629 (2) Ожидание присоединения к бассейну 2013-02-21 06: 37: 53,630 (3) Ожидание присоединения к бассейну 2013-02-21 06: 37: 53,630 (1) Выполняется: ['0', '1'] 2013-02-21 06: 37: 53,730 (0) Выполняется: ['1'] 2013-02-21 06: 37: 53,731 (2) Выполняется: ['1', '2'] 2013-02-21 06: 37: 53,731 (1) Выполняется: ['2'] 2013-02-21 06: 37: 53,732 (3) Выполняется: ['2', '3'] 2013-02-21 06: 37: 53,831 (2) Запуск: ['3'] 2013-02-21 06: 37: 53,833 (3) Выполняется: []
Данные для конкретной резьбы
Хотя некоторые ресурсы необходимо заблокировать, чтобы несколько потоков могли использовать их, другие должны быть защищены, чтобы они были скрыты от глаз в темы, которым они не «принадлежат».Функция local () создает объект, способный скрывать значения от просмотра в отдельных потоках.
случайный импорт импорт потоковой передачи импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def show_value (данные): пытаться: val = data.value кроме AttributeError: logging.debug («Еще нет значения») еще: logging.debug ('значение =% s', значение) def worker (данные): show_value (данные) данные.значение = random.randint (1, 100) show_value (данные) local_data = threading.local () show_value (локальные_данные) local_data.value = 1000 show_value (локальные_данные) для i в диапазоне (2): t = threading.Thread (цель = рабочий, args = (local_data,)) t.start ()
Обратите внимание, что local_data.value не присутствует ни в одном потоке до тех пор, пока он установлен в этом потоке.
$ python threading_local.py (MainThread) Пока нет значения (MainThread) значение = 1000 (Thread-1) Пока нет значения (Поток-1) значение = 34 (Thread-2) Пока нет значения (Поток-2) значение = 7
Чтобы инициализировать настройки, чтобы все потоки начинались с одного и того же значения, используйте подкласс и установите атрибуты в __init __ ().
случайный импорт импорт потоковой передачи импорт журнала logging.basicConfig (уровень = logging.DEBUG, format = '(% (threadName) -10s)% (message) s', ) def show_value (данные): пытаться: val = data.value кроме AttributeError: logging.debug («Еще нет значения») еще: logging.debug ('значение =% s', значение) def worker (данные): show_value (данные) data.value = random.randint (1, 100) show_value (данные) класс MyLocal (threading.local): def __init __ (self, value): протоколирование.отладка ('Инициализация% r', самостоятельно) self.value = значение local_data = MyLocal (1000) show_value (локальные_данные) для i в диапазоне (2): t = threading.Thread (цель = рабочий, args = (local_data,)) t.start ()
__init __ () вызывается для того же объекта (обратите внимание на id () значение), один раз в каждом потоке.
$ питон threading_local_defaults.py (MainThread) Инициализация объекта <__ main __. MyLocal по адресу 0x100514390> (MainThread) значение = 1000 (Thread-1) Инициализация объекта <__ main __. MyLocal по адресу 0x100514390> (Поток-1) значение = 1000 (Поток-2) Инициализация <__ main__.Объект MyLocal по адресу 0x100514390> (Поток-1) значение = 81 (Поток-2) значение = 1000 (Поток-2) значение = 54
См. Также
- резьба
- Документация стандартной библиотеки для этого модуля.
- резьба
- API нижнего уровня резьбы.
- Очередь
- Поточно-ориентированная очередь, полезная для передачи сообщений между потоками.
- многопроцессорность
- API для работы с процессами, который отражает API потоковой передачи.
О наложении бровей | Мэдисонброуз.com
Что такое «нить для бровей»?
Как работает многопоточность?
Болезненно ли наложение бровей?
Сколько времени занимает заправка резьбы?
На какие участки тела можно нарезать резьбу?
Нить лучше, чем воск?
В чем главное преимущество наложения ниток по сравнению с другими методами удаления волос?
Как долго длится заправка нити?
Если мне наложат нитку на все лицо, волосы снова станут темнее?
Почему нитевдеватель засовывает нить ей в рот?
Можно ли нарезать нитку, если у вас чувствительная кожа?
Могу ли я указать, какую форму я хочу получить?
У моих бровей нет естественной дуги, можно ли придать им такую форму?
Я выщипал.Как долго я должен ждать до следующего сеанса потоковой передачи?
Что мне делать, чтобы подготовиться к заправке нитей?
У меня прыщи. Заправка в порядке?
Можно ли нарезать нитку и на мужчин?
Безопасно ли нарезать нитку после косметической операции?
Следует нарезать брови до или после загара?
Что такое «наложение бровей»?
Нитки для бровей - это древняя техника удаления волос, веками применяемая среди самых красивых женщин Азии и Ближнего Востока.Это лучшая альтернатива эпиляции воском и пинцетом, особенно для чувствительной кожи. В отличие от восковой эпиляции, нарезание нитей не удаляет слой кожи, который может сделать кожу лица уязвимой для солнечных лучей. Безусловно, наименее инвазивный метод удаления волос на лице, этот метод создает изящные брови с чистыми, четко очерченными краями, обрамляющими глаз.
Как работает многопоточность?
Здесь, в магазине Brow Boutique, специалист по нарезанию ниток берет тонкую хлопковую нить и скручивает ее в двойную прядь, которая затем проходит по коже.Ритмичным - почти гипнотическим - движением нежелательные волоски закрепляются в нити и извлекаются из фолликула с точностью и изяществом.
Болезненно ли наложение бровей?
Многие из наших клиентов согласны с тем, что наложение нити вызывает меньше дискомфорта, чем восковая эпиляция. Поскольку нить направлена на отдельные волоски, она сводит раздражение кожи к минимуму. Устранено чрезмерное натяжение нежной кожи вокруг глаз, а для тех, кто чувствителен к восковой эпиляции или средствам для депиляции, отсутствует воздействие раздражителей.
Сколько времени занимает заправка резьбы?
Простая нарезка бровей может занять всего 5-7 минут. Верхняя губа занимает около 3 минут; полная лицевая нить занимает около 15-20 минут.
На какие участки тела можно нарезать резьбу?
• Брови
• Бакенбарды
• Выступ
• Подбородок
• Шея
• и пальцы
Нить лучше, чем воск?
Да, как известно, нарезание резьбы лучше, чем пинцет, шугаринг или восковая эпиляция.Именно поэтому в салонах США наблюдается экспоненциальный рост спроса на эту услугу. Этот восточный метод удаления волос вызывает меньший дискомфорт и исключает возможность контакта с воском, слишком горячим для нежной кожи лица. Волосы удаляются от корня, поэтому «фактора роста» меньше. Это увеличивает время между посещениями салона, экономя время, деньги и обслуживание перед зеркалом! … А благодаря точности, «нитевдеватели» могут отточить свое мастерство, создавая идеальные брови, соответствующие уникальной форме и индивидуальности каждого человека.
В чем главное преимущество наложения ниток по сравнению с другими методами удаления волос?
Основное преимуществоThreading - это форма брови - не только снизу, но и сверху. Один из основных недостатков пинцета, особенно когда он выполняется дома, заключается в том, что легко удалить неправильные волоски, создавая дыры или ямки в форме бровей. Некоторые более густые волоски на нижней линии брови действуют как поддерживающие «леса» для всей брови, поэтому, если вы случайно удалите не те волоски, вся форма разрушится.Затем вам остается попытаться замаскировать свою глупость, пока волосы не отрастут снова. Использование потоков устраняет эту распространенную ошибку. Некоторые из других преимуществ:
• Эпиляция воском может тянуть, тянуть, растягиваться и может отслаивать слой кожи. Такое суровое обращение может способствовать развитию морщин. С потоковой передачей этого нет. Вы просто будете выглядеть гладкими, свежими и без волос.
• Менее грязный и трудоемкий, чем восковая эпиляция.
• 100% натуральный; не использовались искусственные воски, химические вещества или инвазивные методы
• Волосы не растут так быстро.
• Кожа обычно не краснеет и не раздражается.
• Заправка нитей может воздействовать на отдельные волоски.
• Волосы должны расти только чуть выше кожи, чтобы их можно было завязать.
• Со временем волосы становятся более тонкими и редкими.
• Заправка нити собирает самые тонкие волоски с поверхности кожи.
• Отличный вариант для тех, кто использует RetinA и Accutane. Рекомендовано дерматологом!
• 100% подходит для кожи, слишком чувствительной для эпиляции воском или лазерной эпиляции
• Многие люди, у которых после эпиляции воском, как правило, не вырываются нити
• Недорогая альтернатива восковой эпиляции или лазеру
Как долго длится заправка нити?
В зависимости от типа волос и области лица полный рост может занять от 2 до 6 недель.Волосы снова станут редкими и тонкими после повторной заправки нити, потому что волосы выдергиваются у корня, который в результате этого процесса ослабляется. Вы можете повторно нарезать нить, как только волосы вырастут чуть выше кожи, тогда как при восковой эпиляции волосы должны быть как минимум на 2/8 дюйма, прежде чем их можно будет снова удалить.
Если мне наложат нитку на все лицо, волосы снова станут темнее?
Нет. Поскольку волосы удаляются от корня, они, как правило, со временем становятся более тонкими.
Почему нитевдеватель засовывает нить ей в рот?
Нить необходимо натянуть туго, чтобы аккуратно и точно удалить каждый волосок. Однако нить, которая касается рта нитевдевателя, никогда не касается вашего лица.
Можно ли нарезать нитку, если у вас чувствительная кожа?
Да. Нанесение нитей рекомендовано дерматологами специально для кожи с ретин-А или аккутаном. Хорошая новость заключается в том, что наложение ниток влияет на кожу меньше, чем другие методы удаления.Однако небольшая розоватость на месте - это нормально, но она должна исчезнуть через 10-15 минут. В редких случаях эта область может оставаться красной от 30 минут до часа. Большинство людей, страдающих высыпаниями из-за восковой эпиляции или резких кремов для депиляции, почувствуют облегчение после перехода на нитью. Если вы считаете, что ваша кожа очень чувствительна, мы рекомендуем не использовать лосьон или гель алоэ, которые обычно наносит ваш специалист по нитке, вместо этого втирайте кубик льда в область с резьбой в течение 10 минут, затем просушите его и воздержитесь от использования каких-либо продуктов для лица для несколько часов, пока эта область не станет менее чувствительной.
Могу ли я указать, какую форму я хочу придать своим бровям?
Да! Перед тем, как начать, сообщите своему специалисту по нитке, какой вы хотите форму бровей и какой толщины или тонкости они вам нужны.
У моих бровей нет естественной дуги, можно ли придать им такую форму?
Совершенно верно, но имейте в виду, что для создания дугообразного вида нашему специалисту по нарезке нитей потребуется удалить больше волос, что сделает брови более тонкими. Если вы хотите, чтобы брови стали более густыми и подходящими к вашим естественным контурам, мы не рекомендуем использовать изогнутый вид.
Я выщипал. Как долго я должен ждать до следующего сеанса потоковой передачи?
Несмотря на то, что волосы можно удалить, когда они выросли над поверхностью кожи, мы рекомендуем подождать около двух недель, чтобы дать вашим бровям возможность снова отрасти, чтобы наш эксперт по нитям мог придать вам желаемую форму. Вы можете прийти в любое время для «уборки».
Что мне делать, чтобы подготовиться к заправке потоков?
Пусть ваши волосы вырастут прямо над кожей, и вы готовы.Если вы хотите придать бровям новую или иную форму, дайте им немного вырасти, но не выщипывайте их, чтобы наш эксперт по ниткам мог увидеть их естественную форму, прежде чем предложить лучшую форму для вашего лица.
У меня прыщи. Заправка в порядке?
Да. Никаких побочных эффектов быть не должно. На самом деле, большинство людей, у которых возникают высыпания после депиляции воском, обнаруживают, что нарезание нитей не дает того же эффекта. Это абсолютно гигиенично, поэтому не бойтесь попробовать эту услугу.
Можно ли нарезать нитку и на мужчин?
Да! На самом деле, мы поощряем наложение бровей у мужчин, потому что оно сохраняет естественный вид мужских бровей, не выглядя чрезмерно или даже вовсе не сделанным. Независимо от того, хотите ли вы отделить однобровь или проредить густые брови, нарезание нитей даст вам чистую линию бровей, которая идеально подойдет для вашего следующего свидания, снимка головы или важного собеседования.
Безопасно ли нарезать резьбу после косметической операции?
Посоветуйтесь со своим врачом о конкретном времени заживления для вашей процедуры и узнайте, сколько времени вам следует ждать, прежде чем возобновить заправку нити.
Следует нарезать брови до или после загара?
Загар перед нарезкой резьбы - это нормально. Однако сразу после нарезания нити кожа слишком чувствительна, чтобы ее можно было использовать в солярии. Перед тем, как загореть, рекомендуется подождать не менее 48 часов после наведения бровей.
Что такое нарезание бровей? - Что нужно знать и следует ли использовать нить для коррекции бровей
Тенденции бровей приходят и уходят, но предпочитаете ли вы аккуратную и аккуратную дугу или какую-нибудь винтажную смелость Брук Шилдс для ваших бровей, почти все из нас иногда нуждаются в небольшом формировании и чистке.И хотя вот несколько классических способов усовершенствования свода стопы - сломать пинцет или потянуться за воском, - это не единственные варианты.
Нитки, многовековой процесс удаления волос, происходящий из Южной Азии и Ближнего Востока, уже много лет набирает популярность. Так в чем же вся суета? Мы постарались ответить на все ваши животрепещущие вопросы о переносе бровей, чтобы вы могли выбрать подходящий для вас метод удаления.
Как работает нитка для бровей?
Нарезка резьбы - это правильно.Фактически, инструмент для удаления волос состоит из не более чем нити, зажатой между руками техника (а в некоторых случаях и зубами) в скрученной конфигурации. По мере того, как техник двигает руками, промежутки между этими завитками открываются, а затем снова затягиваются, хватая и удерживая волосы, и выдергивая их с корнем и всем остальным. Если вы когда-либо использовали эпилятор раньше, вы знакомы с основной концепцией.
Чем это отличается от эпиляции воском или пинцетом?
Есть несколько причин, по которым любители резьбонарезания предпочитают отказаться от пинцета и отменить назначения на восковую эпиляцию.Первый сводится к точности.
«Заправка нитей очень точна и позволяет нашим специалистам лучше контролировать, какие волоски удаляются», - говорит Шобха Туммала, основатель центра удаления волос Шобха. Поэтому там, где воск удаляет любые пряди волос, с которыми он соприкасается, что иногда может приводить к резким линиям, нарезание нитей позволяет получить более естественный вид. Джаймини Патель, руководитель отдела обучения в Blink Brow Bar в Лондоне, добавляет: «Техника хлопковой нити позволяет вам диктовать волосы, которые вы хотите сохранить или удалить, что означает идеальную форму каждый раз.«
BiddibooGetty Images
Фактор волос, конечно, жизненно важен для удаления волос, еще одна область, в которой выделяется нитка, - это его воздействие на кожу или, скорее, ее отсутствие.
Нитка «не тянет и не режет кожу, как баночка для воска или пинцета», - говорит Патель. Видите ли, воски обычно содержат тепло, а также химические вещества (натуральные или синтетические), которые могут вызывать высыпание некоторых типов кожи, а процесс удаления иногда может повредить кожу, что приведет к покраснению и шелушению, особенно для тех, кто использует средства для ухода за кожей, такие как ретинол.Не совсем то, на что многие из нас надеются, когда намереваются сделать брови.
Хотя выщипывание, как правило, менее опасно для кожи, чем восковая эпиляция, случайное протыкание, царапание или натяжение кожи пинцетом (особенно нестерилизованная пара, находящаяся в вашей аптечке) может привести к разрывам кожи и даже инфекциям. . Поскольку нить выполняется с мягкими волокнами на уровне поверхности, она не может случайно зацепиться за край фолликула или поцарапать кожу.
На самом деле Туммала настолько верит в преимущества безопасности, которые дает нарезка бровей, что удваивает свои усилия в своих локациях Шобха.«Мы так твердо верим в этот метод, что это единственный вариант удаления волос, который мы предлагаем для деликатной области вокруг глаз», - говорит она.
Изображения JGIGetty
Больно ли протирать брови?
Короткий ответ: да. Хотя некоторые утверждают, что наложение ниток менее болезненно, чем выщипывание или восковая эпиляция, поскольку оно быстрое и не связано с натягиванием кожи, реальность такова, что любая форма удаления волос, которая вытягивает волосы от корня, будет сопряжена с некоторой болью. .Извините.
Сколько стоит нарезка резьбы?
Очевидно, это зависит от того, куда вы идете и от состояния ваших бровей, но типичный 10-20-минутный сеанс потоковой передачи браузера может длиться от 15 до 45 долларов.
Как долго сохраняются результаты заправки?
Волосы у всех растут по-разному, но в среднем удаление волос с помощью нитки может длиться от 2 до 5 недель. Вы также можете дольше сохранять внешний вид с помощью ретуши, которая менее интенсивна, чем полноценное моделирование бровей.
Виктория Лабади - ФотономадаGetty Images
Подходит ли нить для всех типов кожи и волос?
По сути, да. Отсутствие повреждений кожи и химических веществ делает нитку подходящей для тех, кто принимает лекарства, которые могут вызвать чувствительность кожи, такие как ретиноиды и некоторые лекарства от прыщей, а также для тех, кто склонен к высыпанию после восковой эпиляции. Точно так же нарезание ниток отлично подходит для захвата даже самых крошечных волосков (Туммала даже предлагает это для персикового пуха или коротких щетинистых волос перед большим событием), поэтому волосы с тонкими бровями будут посажены.
Исключения? Всем, кто испытывает повышенную чувствительность (например, после химического пилинга или микродермабразии), а также тем, кто недавно перенес косметическую операцию, следует сесть и снять нитку. «Мы рекомендуем сначала проконсультироваться с вашим врачом, а затем вернуться к нам через 3-4 недели после операции для назначения нитей, чтобы у вашей кожи было время на заживление», - говорит Туммала.
Есть ли побочные эффекты от многопоточности?
Как и при любой другой форме удаления волос, покраснение и высыпание - самые большие побочные эффекты от нарезания нитей.Покраснение обычно проходит в течение часа или двух после наложения ниток (так что не планируйте сразу перед вечеринкой), но вы можете помочь успокоиться с помощью противовоспалительного крема, такого как гидрокортизон или старый добрый лед.
Что касается высыпаний, все сводится к тщательному уходу за вновь обнаженными бровями. «После заправки нити ваши поры будут широко открыты для раздражения, поэтому важно не прикасаться к области с резьбой», - предупреждает Патель. «В течение следующих 24 часов мы также рекомендуем по возможности избегать бассейнов, саун, парных, высоких температур и прямых солнечных лучей.«А для достижения оптимальных результатов, возможно, отдохните от своей косметической рутины». Спрей для загара может закупорить поры, как и определенный макияж, поэтому будьте осторожны при нанесении макияжа на брови и старайтесь избегать вообще, если у вас были другие части лица с резьбой ", - говорит она.
Лорен Хаббард Писатель Лорен Хаббард - писатель-фрилансер и участник проекта Town & Country, который освещает темы красоты, шоппинга, развлечений, путешествий, домашнего декора, вина и коктейлей.Этот контент создается и поддерживается третьей стороной и импортируется на эту страницу, чтобы помочь пользователям указать свои адреса электронной почты. Вы можете найти больше информации об этом и подобном контенте на сайте piano.io.
История наращивания бровей | Нить для бровей
Всего несколько лет назад наложение ниток было загадкой для большей части Америки. Используя хлопковую нить, чтобы придать форму бровям? Это звучало безумно. Западный мир выбрал пинцет, воск и даже бритвы для создания форм (и зазоров), которые заставят вздрогнуть любого художника, занимающегося продвижением бровей.Затем появилась Ziba Beauty.
Еще в 1980-х годах в маленьком уголке Калифорнии Ziba Beauty была одной из первых, кто начал американскую революцию в области бровей. Наше скромное начало в крошечном салоне быстро научило женщин любого происхождения тому, что нитки - лучший и самый эффективный способ создать красивые аккуратные брови.
Теперь, к счастью, нитки проникли в западный мир и стали техникой, которую многие женщины и мужчины с радостью включили в свой обычный режим красоты.Это широко признанный один из самых быстрых и точных способов удаления волос на лице и придания формы бровям!
Итак, это ваше двухминутное руководство по истории создания потоков. Что-то, что мы считаем формой искусства Древнего Востока, должно выполняться только экспертами.
Хотя точной даты начала заправки нити нет, считается, что этой практике 6000 лет. Происходя из восточных культур Индии и Ирана, наложение ниток было для женщин способом удаления нежелательных волосков и создания безупречной формы бровей.Считается также, что китаянки предпочитают удаление волос нитками любой другой форме.
The Art Of Threading® было частью вековых традиций и обрядов посвящения. Например, в Персии считалось, что наложение бровей отмечает путь женщины во взрослую жизнь. У молодой женщины наложили брови прямо перед тем, как выйти замуж, чтобы обозначить ее новый статус жены. В других частях восточной культуры говорят, что фигурная бровь была признаком женской мужественности.
Нить считалась доступной роскошью, и практикующие использовали хлопковую нить, скрученную и скрученную петлями, для создания идеальной формы бровей.Но, несмотря на легкость и доступность хлопковой нити, не обманывайтесь, думая, что кто-то может продеть вам брови. Чтобы создать точную дугу или красивую кривую, требуется как обучение, так и навыки.
Все мастера красоты Ziba прошли обучение по 9-ступенчатому протоколу, который называется «Наше обещание». Этот метод совершенствовался на протяжении многих лет и является основой искусства нитей Ziba Beauty. Мы считаем, что брови каждого клиента должны подходить к его лицу и форме глаз, и мы поможем вам создать правильный образ.
Нить для бровей - это не только мода: с нее начинаются естественно красивые брови, и Ziba Beauty поможет вам в этом.
Введение в многопоточность в Python - настоящий Python
Смотреть сейчас В этом руководстве есть связанный видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Threading in Python
Потоковая обработка Python позволяет вам запускать разные части вашей программы одновременно и может упростить ваш дизайн.Если у вас есть некоторый опыт работы с Python и вы хотите ускорить свою программу с помощью потоков, то это руководство для вас!
Из этой статьи вы узнаете:
- Какие темы
- Как создавать потоки и ждать их завершения
- Как использовать
ThreadPoolExecutor
- Как избежать состояния гонки
- Как использовать общие инструменты, которые предоставляет Python
threading
В этой статье предполагается, что вы знакомы с основами Python и используете как минимум версию 3.6 для запуска примеров. Если вам нужно освежиться, вы можете начать с Пути обучения Python и быстро освоиться.
Если вы не уверены, хотите ли вы использовать Python threading
, asyncio
или multiprocessing
, вы можете проверить ускорение вашей программы Python с помощью параллелизма.
Все источники, используемые в этом руководстве, доступны вам в репозитории Real Python на GitHub.
Бесплатный бонус: 5 мыслей о Python Mastery, бесплатный курс для разработчиков Python, который показывает вам план действий и образ мышления, которые вам понадобятся, чтобы вывести свои навыки Python на новый уровень.
Пройдите тест: Проверьте свои знания с помощью нашей интерактивной викторины «Python Threading». По завершении вы получите оценку, чтобы вы могли отслеживать свой прогресс в обучении с течением времени:
Пройти тест »
Что такое нить?
Поток - это отдельный поток выполнения. Это означает, что в вашей программе будут происходить сразу две вещи. Но для большинства реализаций Python 3 разные потоки на самом деле не выполняются одновременно: они просто кажутся.
Заманчиво думать о многопоточности как о двух (или более) разных процессорах, работающих над вашей программой, каждый из которых одновременно выполняет независимую задачу. Это почти верно. Потоки могут выполняться на разных процессорах, но они будут выполняться только по одному.
Для одновременного выполнения нескольких задач требуется нестандартная реализация Python, написание части кода на другом языке или использование многопроцессорной обработки
, что требует дополнительных накладных расходов.
Из-за того, как работает реализация Python в CPython, многопоточность не может ускорить все задачи. Это связано с взаимодействиями с GIL, которые существенно ограничивают одновременный запуск одного потока Python.
Задачи, которые проводят большую часть своего времени в ожидании внешних событий, обычно являются хорошими кандидатами для многопоточности. Проблемы, требующие интенсивных вычислений ЦП и тратящих мало времени на ожидание внешних событий, могут вообще не работать быстрее.
Это верно для кода, написанного на Python и работающего в стандартной реализации CPython.Если ваши потоки написаны на C, они могут выпускать GIL и работать одновременно. Если вы используете другую реализацию Python, ознакомьтесь с документацией, чтобы узнать, как она обрабатывает потоки.
Если вы используете стандартную реализацию Python, пишете только на Python, и имеете проблему, связанную с процессором, вам следует вместо этого проверить многопроцессорный модуль .
Архитектура вашей программы с использованием многопоточности также может обеспечить повышение ясности проектирования.Большинство примеров, о которых вы узнаете в этом руководстве, не обязательно будут выполняться быстрее, потому что они используют потоки. Использование в них резьбы помогает сделать дизайн более понятным и понятным.
Итак, давайте прекратим говорить о потоках и начнем их использовать!
Начало потока
Теперь, когда вы получили представление о нити, давайте узнаем, как ее создать. Стандартная библиотека Python предоставляет потоков
, которые содержат большинство примитивов, которые вы увидите в этой статье. Поток
в этом модуле красиво инкапсулирует потоки, обеспечивая чистый интерфейс для работы с ними.
Чтобы запустить отдельный поток, вы создаете экземпляр Thread
, а затем сообщаете ему .start ()
:
1 импортный журнал
2импорт резьбы
3время импорта
4
5def функция_потока (имя):
6 logging.info ("Тема% s: запускается", имя)
7 раз. Сон (2)
8 logging.info ("Тема% s: завершение", имя)
9
10if __name__ == "__main__":
11 format = "% (asctime) s:% (message) s"
12 лесозаготовок.basicConfig (формат = формат, уровень = logging.INFO,
13 datefmt = "% H:% M:% S")
14
15 logging.info («Главное: перед созданием потока»)
16 x = threading.Thread (target = функция_потока, args = (1,))
17 logging.info («Главное: перед запуском потока»)
18 x.start ()
19 logging.info («Главное: дождаться завершения потока»)
20 # x.join ()
21 logging.info («Главное: все готово»)
Если вы посмотрите на операторы регистрации, вы увидите, что основной раздел
создает и запускает поток:
x = нарезание резьбы.Поток (target = thread_function, args = (1,))
x.start ()
Когда вы создаете поток Thread
, вы передаете ему функцию и список, содержащий аргументы этой функции. В этом случае вы указываете потоку Thread
запустить thread_function ()
и передать ему 1
в качестве аргумента.
В этой статье вы будете использовать последовательные целые числа в качестве имен для ваших цепочек. Существует threading.get_ident ()
, который возвращает уникальное имя для каждого потока, но они обычно не короткие и не легко читаемые.
thread_function ()
сам по себе мало что делает. Он просто регистрирует некоторые сообщения с time.sleep ()
между ними.
Когда вы запустите эту программу как есть (с закомментированной строкой двадцать), результат будет выглядеть так:
$ ./single_thread.py
Основная: перед созданием потока
Основной: перед запуском потока
Поток 1: начало
Main: дождитесь завершения потока
Главное: все готово
Поток 1: отделка
Вы заметите, что поток Thread
завершился после того, как завершился раздел Main
вашего кода.Вы вернетесь к тому, почему это так, и поговорите о таинственной двадцатой строке в следующем разделе.
Демонические потоки
В информатике, демон
- это процесс, который выполняется в фоновом режиме.
Python threading
имеет более конкретное значение для демона
. Демон поток
будет завершен сразу после выхода из программы. Один из способов подумать об этих определениях - рассмотреть поток демона
как поток, который работает в фоновом режиме, не беспокоясь о его завершении.
Если программа выполняет потоков
, которые не являются демонами
, то программа будет ждать завершения этих потоков, прежде чем завершиться. Потоки
, которые являются демонами , однако просто уничтожаются, где бы они ни находились, при выходе из программы.
Давайте внимательнее посмотрим на результат вашей программы выше. Последние две строчки представляют интерес. Когда вы запустите программу, вы заметите паузу (около 2 секунд) после того, как __main__
напечатал свое сообщение all done
, но до завершения цепочки.
Эта пауза - это Python, ожидающий завершения недемонического потока. Когда ваша программа Python завершается, частью процесса выключения является очистка подпрограммы потоковой передачи.
Если вы посмотрите на исходный код Python threading
, вы увидите, что threading._shutdown ()
проходит через все запущенные потоки и вызывает .join ()
для всех, у которых нет демона Установлен флаг
.
Итак, ваша программа ожидает выхода, потому что сам поток ожидает в спящем режиме.Как только он завершит и напечатает сообщение, .join ()
вернется, и программа сможет выйти.
Часто это именно то, что вам нужно, но у нас есть и другие варианты. Давайте сначала повторим программу с потоком демона
. Это можно сделать, изменив способ построения потока
, добавив флаг daemon = True
:
x = threading.Thread (target = thread_function, args = (1,), демон = True)
Теперь, когда вы запустите программу, вы должны увидеть следующий результат:
$./daemon_thread.py
Основная: перед созданием потока
Основной: перед запуском потока
Поток 1: начало
Main: дождитесь завершения потока
Главное: все готово
Разница в том, что последняя строка вывода отсутствует. thread_function ()
не удалось выполнить. Это был поток демона , поэтому, когда
__main__
достиг конца своего кода и программа хотела завершить работу, демон был убит.
соединение ()
a РезьбаДемонические потоки удобны, но как насчет того, чтобы дождаться остановки потока? Как насчет того, чтобы сделать это и не выходить из программы? Теперь давайте вернемся к исходной программе и посмотрим на эту закомментированную строку двадцать:
. Чтобы указать одному потоку дождаться завершения другого потока, вы вызываете .присоединиться ()
. Если вы раскомментируете эту строку, основной поток остановится и будет ждать завершения потока x
.
Вы проверяли это на коде с потоком демона или обычным потоком? Оказывается, это неважно. Если вы .join ()
поток, этот оператор будет ждать, пока любой из потоков не завершится.
Работа с множеством потоков
Пример кода до сих пор работал только с двумя потоками: основным потоком и тем, который вы начали с потоковой передачи .Объект Thread
.
Часто возникает необходимость запустить несколько потоков, чтобы они выполняли интересную работу. Давайте начнем с более сложного способа сделать это, а затем перейдем к более легкому методу.
Более сложный способ запуска нескольких потоков - тот, который вы уже знаете:
импорт журнала
импорт потоковой передачи
время импорта
def thread_function (имя):
logging.info ("Тема% s: запускается", имя)
время сна (2)
logging.info ("Тема% s: завершение", имя)
если __name__ == "__main__":
format = "% (asctime) s:% (сообщение) s"
протоколирование.basicConfig (формат = формат, уровень = logging.INFO,
datefmt = "% H:% M:% S")
потоки = список ()
для индекса в диапазоне (3):
logging.info ("Main: создать и запустить поток% d.", index)
x = threading.Thread (target = thread_function, args = (индекс,))
threads.append (x)
x.start ()
для индекса поток в перечислении (потоки):
logging.info ("Main: перед присоединением к потоку% d.", index)
thread.join ()
logging.info ("Основная часть: поток% d завершен", индекс)
В этом коде используется тот же механизм, который вы видели выше, для запуска потока, создания объекта Thread
и последующего вызова .Старт ()
. Программа хранит список объектов Thread
, чтобы затем ждать их позже, используя .join ()
.
Многократный запуск этого кода, вероятно, даст интересные результаты. Вот пример вывода с моей машины:
$ ./multiple_threads.py
Основной: создать и запустить поток 0.
Поток 0: начало
Основной: создать и запустить поток 1.
Поток 1: начало
Основной: создать и запустить поток 2.
Поток 2: начало
Основной: перед присоединением к потоку 0.Поток 2: отделка
Поток 1: отделка
Поток 0: отделка
Основной: поток 0 завершен
Основной: перед присоединением к потоку 1.
Основной: поток 1 завершен
Основной: перед присоединением к потоку 2.
Основной: поток 2 выполнен
Если вы внимательно просмотрите выходные данные, вы увидите, что все три потока запускаются в ожидаемом порядке, но в этом случае они заканчиваются в обратном порядке! Несколько прогонов приведут к разному порядку. Найдите сообщение Thread x: finish
, которое сообщит вам, когда каждая цепочка завершена.
Порядок выполнения потоков определяется операционной системой и его довольно сложно предсказать. Это может (и, вероятно, будет) варьироваться от запуска к запуску, поэтому вам нужно знать об этом, когда вы разрабатываете алгоритмы, использующие потоки.
К счастью, Python предоставляет вам несколько примитивов, которые вы рассмотрите позже, чтобы помочь координировать потоки и заставить их работать вместе. Перед этим давайте посмотрим, как немного упростить управление группой потоков.
Использование
ThreadPoolExecutor
Есть более простой способ создать группу потоков, чем тот, который вы видели выше.Он называется ThreadPoolExecutor
и является частью стандартной библиотеки в concurrent.futures
(начиная с Python 3.2).
Самый простой способ создать его - в качестве диспетчера контекста, используя оператор с
для управления созданием и уничтожением пула.
Вот __main__
из последнего примера, переписанный для использования ThreadPoolExecutor
:
импорт одновременных фьючерсов
# [остальной код]
если __name__ == "__main__":
format = "% (asctime) s:% (сообщение) s"
протоколирование.basicConfig (формат = формат, уровень = logging.INFO,
datefmt = "% H:% M:% S")
с concurrent.futures.ThreadPoolExecutor (max_workers = 3) в качестве исполнителя:
executeor.map (функция_потока, диапазон (3))
Код создает ThreadPoolExecutor
в качестве диспетчера контекста, сообщая ему, сколько рабочих потоков он хочет в пуле. Затем он использует .map ()
для пошагового выполнения итерации вещей, в вашем случае range (3)
, передавая каждое из них потоку в пуле.
Конец с блоком
заставляет ThreadPoolExecutor
выполнить .join ()
для каждого из потоков в пуле. Настоятельно рекомендуется использовать ThreadPoolExecutor
в качестве диспетчера контекста, когда это возможно, чтобы вы никогда не забыли .join ()
потоки.
Примечание: Использование ThreadPoolExecutor
может вызвать некоторые сбивающие с толку ошибки.
Например, если вы вызываете функцию, которая не принимает параметров, но передаете ей параметры в .map ()
, поток выдаст исключение.
К сожалению, ThreadPoolExecutor
скроет это исключение, и (в случае выше) программа завершится без вывода. Поначалу это может быть довольно запутанным при отладке.
При запуске исправленного кода примера будет получен следующий результат:
$ ./executor.py
Поток 0: начало
Поток 1: начало
Поток 2: начало
Поток 1: отделка
Поток 0: отделка
Поток 2: отделка
Снова обратите внимание на то, что Thread 1
завершился до Thread 0
.Планирование потоков выполняется операционной системой и не следует плану, который легко понять.
Условия гонки
Прежде чем перейти к другим функциям, скрытым в Python threading
, давайте немного поговорим об одной из наиболее сложных проблем, с которыми вы столкнетесь при написании многопоточных программ: условиях гонки.
После того, как вы увидели, что такое состояние гонки, и взглянули на то, что происходит, вы перейдете к некоторым примитивам, предоставляемым стандартной библиотекой, чтобы предотвратить возникновение состояний гонки.
Состояния состязания могут возникать, когда два или более потока обращаются к совместно используемому фрагменту данных или ресурсу. В этом примере вы собираетесь создать большое состояние гонки, которое происходит каждый раз, но имейте в виду, что большинство условий гонки не так очевидны. Часто они возникают редко и могут привести к запутанным результатам. Как вы понимаете, это затрудняет их отладку.
К счастью, это состояние гонки будет происходить каждый раз, и вы подробно рассмотрите его, чтобы объяснить, что происходит.
В этом примере вы собираетесь написать класс, который обновляет базу данных. Ладно, на самом деле у вас не будет базы данных: вы просто собираетесь подделать ее, потому что не в этом суть этой статьи.
В вашем FakeDatabase
будут методы .__ init __ ()
и .update ()
:
класс FakeDatabase:
def __init __ (сам):
self.value = 0
def update (self, name):
logging.info ("Тема% s: начало обновления", имя)
local_copy = self.ценить
local_copy + = 1
time.sleep (0,1)
self.value = local_copy
logging.info ("Тема% s: завершение обновления", имя)
FakeDatabase
отслеживает один номер: . Значение
. Это будут общие данные, на которых вы увидите состояние гонки.
.__ init __ ()
просто инициализирует .value
нулевым значением. Все идет нормально.
.update ()
выглядит немного странно. Он имитирует чтение значения из базы данных, выполнение над ним некоторых вычислений, а затем запись нового значения обратно в базу данных.
В этом случае чтение из базы данных означает просто копирование .value
в локальную переменную. Вычисление просто прибавляет единицу к значению, а затем немного .sleep ()
. Наконец, он записывает значение обратно, копируя локальное значение обратно в .value
.
Вот как вы будете использовать эту FakeDatabase
:
, если __name__ == "__main__":
format = "% (asctime) s:% (сообщение) s"
logging.basicConfig (формат = формат, уровень = ведение журнала.ИНФОРМАЦИЯ,
datefmt = "% H:% M:% S")
база данных = FakeDatabase ()
logging.info («Тестирование обновления. Начальное значение% d.», database.value)
с concurrent.futures.ThreadPoolExecutor (max_workers = 2) в качестве исполнителя:
для индекса в диапазоне (2):
исполнитель.submit (база данных.обновление, индекс)
logging.info («Тестирование обновления. Конечное значение% d.», database.value)
Программа создает ThreadPoolExecutor
с двумя потоками, а затем вызывает .submit ()
для каждого из них, сообщая им запустить database.update ()
.
.submit ()
имеет подпись, которая позволяет передавать как позиционные, так и именованные аргументы функции, работающей в потоке:
.submit (функция, * аргументы, ** kwargs)
В приведенном выше использовании индекс
передается в качестве первого и единственного позиционного аргумента функции database.update ()
. Позже в этой статье вы увидите, что аналогичным образом можно передавать несколько аргументов.
Поскольку каждый поток запускает .update ()
, а .update ()
добавляет единицу к .value
, можно ожидать, что database.value
будет 2
, когда он будет распечатан в конце. Но если бы это было так, вы бы не смотрели на этот пример. Если вы запустите приведенный выше код, результат будет выглядеть так:
$ ./racecond.py
Тестирование разблокированного обновления. Начальное значение 0.
Тема 0: начало обновления
Тема 1: начало обновления
Тема 0: завершение обновления
Тема 1: завершение обновления
Тестирование разблокированного обновления.Конечное значение - 1.
Вы, возможно, ожидали, что это произойдет, но давайте рассмотрим детали того, что здесь происходит на самом деле, так как это упростит понимание решения этой проблемы.
Одна нить
Прежде чем углубиться в эту проблему с двумя потоками, давайте сделаем шаг назад и немного поговорим о некоторых деталях того, как работают потоки.
Вы не будете вдаваться в подробности здесь, поскольку на данном уровне это не важно. Мы также упростим некоторые вещи таким образом, чтобы они не были технически точными, но которые дадут вам правильное представление о том, что происходит.
Когда вы указываете своему ThreadPoolExecutor
запускать каждый поток, вы сообщаете ему, какую функцию запускать и какие параметры передать ей: executor.submit (database.update, index)
.
В результате каждый из потоков в пуле будет вызывать database.update (index)
. Обратите внимание, что база данных
является ссылкой на один объект FakeDatabase
, созданный в __main__
. Вызов .update ()
для этого объекта вызывает метод экземпляра для этого объекта.
Каждый поток будет иметь ссылку на один и тот же объект FakeDatabase
, базу данных
. Каждый поток также будет иметь уникальное значение, , индекс
, чтобы облегчить чтение операторов журнала:
Когда поток запускает .update ()
, он имеет свою собственную версию всех данных , локальных для функции. В случае .update ()
это local_copy
. Это определенно хорошо. В противном случае два потока, выполняющие одну и ту же функцию, всегда будут путать друг друга.Это означает, что все переменные, привязанные к функции (или локальные), являются потокобезопасными .
Теперь вы можете начать изучение того, что произойдет, если вы запустите указанную выше программу с одним потоком и одним вызовом .update ()
.
На изображении ниже показано выполнение .update ()
, если выполняется только один поток. Слева показан оператор, за которым следует диаграмма, показывающая значения в local_copy
и общей базе данных .значение
:
Диаграмма построена таким образом, что время увеличивается по мере продвижения сверху вниз. Он начинается, когда создается Thread 1
, и заканчивается, когда он завершается.
При запуске потока Thread 1
значение FakeDatabase.value
равно нулю. Первая строка кода в методе local_copy = self.value
копирует нулевое значение в локальную переменную. Затем он увеличивает значение local_copy
с помощью оператора local_copy + = 1
.Вы можете увидеть, что .value
в Thread 1
устанавливается в единицу.
Следующий вызывается time.sleep ()
, который приостанавливает текущий поток и позволяет другим потокам выполняться. Поскольку в этом примере только один поток, это не имеет никакого эффекта.
Когда Поток 1
просыпается и продолжает работу, он копирует новое значение из local_copy
в FakeDatabase.value
, а затем поток завершается. Вы можете видеть, что database.value
установлен в единицу.
Пока все хорошо. Вы выполнили .update ()
один раз, а значение FakeDatabase.value
увеличилось до единицы.
Две нити
Возвращаясь к состоянию гонки, два потока будут работать одновременно, но не одновременно. У каждого из них будет своя собственная версия local_copy
, и каждый будет указывать на одну и ту же базу данных
. Именно этот общий объект базы данных будет вызывать проблемы.
Программа начинается с Поток 1
выполняется .update ()
:
Когда Thread 1
вызывает time.sleep ()
, он позволяет другому потоку начать работу. Здесь все становится интересно.
Поток 2
запускается и выполняет те же операции. Он также копирует database.value
в свой частный local_copy
, и этот общий database.value
еще не обновлен:
Когда Thread 2
наконец переходит в спящий режим, общая база данных .значение
все еще не изменено и равно нулю, и обе частные версии local_copy
имеют значение один.
Поток 1
теперь просыпается и сохраняет свою версию local_copy
, а затем завершается, давая Потоку 2
последний шанс на выполнение. Поток 2
не знает, что Поток 1
выполнил и обновил database.value
, пока он спал. Он сохраняет свою версию local_copy
в базе данных .значение
, также установив его на единицу:
Два потока имеют чередующийся доступ к одному общему объекту, перезаписывая результаты друг друга. Подобные условия гонки могут возникнуть, когда один поток освобождает память или закрывает дескриптор файла до того, как другой поток завершит доступ к нему.
Почему это не глупый пример
Приведенный выше пример создан для того, чтобы гарантировать, что состояние гонки возникает каждый раз, когда вы запускаете свою программу. Поскольку операционная система может поменять местами поток в любое время, можно прервать такой оператор, как x = x + 1
, после того, как он прочитает значение x
, но до того, как он вернет увеличенное значение.
Подробности того, как это происходит, довольно интересны, но не нужны для остальной части этой статьи, поэтому не стесняйтесь пропустить этот скрытый раздел.
Приведенный выше код не так уж и прост, как вы могли подумать. Он был разработан, чтобы вызывать состояние гонки каждый раз, когда вы его запускаете, но это значительно упрощает решение, чем большинство условий гонки.
При рассмотрении условий гонки следует иметь в виду две вещи:
Даже такая операция, как
x + = 1
, требует от процессора множества шагов.Каждый из этих шагов представляет собой отдельную инструкцию для процессора.Операционная система может поменять местами поток, на котором выполняется , в любое время . Нить можно поменять местами после любой из этих небольших инструкций. Это означает, что поток можно перевести в спящий режим, чтобы позволить другому потоку работать в середине оператора Python.
Давайте рассмотрим это подробнее. В REPL ниже показана функция, которая принимает параметр и увеличивает его:
>>> >>> def inc (x):
... x + = 1
...
>>> import dis
>>> dis.dis (inc)
2 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_FAST 0 (x)
8 LOAD_CONST 0 (Нет)
10 RETURN_VALUE
В примере REPL используется dis
из стандартной библиотеки Python, чтобы показать более мелкие шаги, которые процессор выполняет для реализации вашей функции. Он выполняет LOAD_FAST
для значения данных x
, он выполняет LOAD_CONST 1
, а затем использует INPLACE_ADD
для сложения этих значений.
Мы останавливаемся здесь по особой причине. Это точка в .update ()
выше, где time.sleep ()
заставил потоки переключиться. Вполне возможно, что время от времени операционная система будет переключать потоки в этот точный момент, даже без sleep ()
, но вызов sleep ()
заставляет это происходить каждый раз.
Как вы узнали выше, операционная система может менять потоки в любое время. Вы перешли по этому списку к выписке с пометкой 4
.Если операционная система заменяет этот поток и запускает другой поток, который также изменяет x
, то, когда этот поток возобновляется, он перезапишет x
с неправильным значением.
Технически в этом примере не будет состояния гонки, потому что x
является локальным для inc ()
. Однако он показывает, как поток может быть прерван во время одной операции Python. Тот же набор операций LOAD, MODIFY, STORE также выполняется с глобальными и общими значениями.Вы можете исследовать с помощью модуля dis
и доказать это сами.
Такое состояние гонки встречается редко, но помните, что редкое событие, происходящее за миллионы итераций, может произойти. Редкость этих состояний гонки делает их намного труднее отлаживать, чем обычные ошибки.
А теперь вернемся к регулярному обучению!
Теперь, когда вы увидели состояние гонки в действии, давайте узнаем, как их решить!
Базовая синхронизация с использованием блокировки
Есть несколько способов избежать или решить условия гонки.Здесь вы не будете рассматривать их все, но некоторые из них используются часто. Начнем с Lock
.
Чтобы решить указанное выше состояние гонки, вам нужно найти способ разрешить только одному потоку за раз в раздел чтения-изменения-записи вашего кода. Самый распространенный способ сделать это в Python называется Lock
. В некоторых других языках эта же идея называется мьютексом
. Mutex происходит от MUTual Exclusion, что и делает блокировка Lock
.
A Замок
- это объект, который действует как проход через холл. Только один поток одновременно может иметь Lock
. Любой другой поток, которому требуется блокировка Lock
, должен дождаться, пока владелец блокировки Lock
откажется от него.
Основные функции для этого: .acquire ()
и .release ()
. Поток вызовет my_lock.acquire ()
, чтобы получить блокировку. Если блокировка уже удерживается, вызывающий поток будет ждать, пока она не будет снята.Здесь есть важный момент. Если один поток получит блокировку, но никогда не вернет ее, ваша программа застрянет. Подробнее об этом вы узнаете позже.
К счастью, Python Lock
также будет работать как диспетчер контекста, поэтому вы можете использовать его в операторе с
, и он автоматически освобождается, когда с блоком
завершается по любой причине.
Давайте посмотрим на FakeDatabase
с добавленным блокировкой Lock
. Вызывающая функция остается прежней:
класс FakeDatabase:
def __init __ (сам):
себя.значение = 0
self._lock = threading.Lock ()
def locked_update (я, имя):
logging.info ("Тема% s: начало обновления", имя)
logging.debug ("Тема% s собирается заблокировать", имя)
с self._lock:
logging.debug ("Поток% s заблокирован", имя)
local_copy = self.value
local_copy + = 1
time.sleep (0,1)
self.value = local_copy
logging.debug ("Тема% s собирается снять блокировку", имя)
logging.debug ("Тема% s после выпуска", имя)
протоколирование.info ("Тема% s: завершение обновления", имя)
Помимо добавления кучи журналов отладки, чтобы вы могли более четко видеть блокировку, большим изменением здесь является добавление члена с именем ._lock
, который является объектом threading.Lock ()
. Этот ._lock
инициализируется в разблокированном состоянии, блокируется и освобождается с помощью оператора
.
Здесь стоит отметить, что поток, выполняющий эту функцию, будет удерживать эту блокировку Lock
до тех пор, пока полностью не завершит обновление базы данных.В этом случае это означает, что он будет удерживать блокировку Lock
во время копирования, обновления, ожидания, а затем записывает значение обратно в базу данных.
Если вы запустите эту версию с настройкой ведения журнала на уровень предупреждения, вы увидите следующее:
$ ./fixrace.py
Тестирование заблокированного обновления. Начальное значение 0.
Тема 0: начало обновления
Тема 1: начало обновления
Тема 0: завершение обновления
Тема 1: завершение обновления
Тестирование заблокированного обновления. Конечное значение - 2.
Посмотрите на это. Ваша программа наконец-то заработала!
Вы можете включить полное ведение журнала, установив уровень DEBUG
, добавив этот оператор после настройки вывода журнала в __main__
:
лесозаготовка.getLogger (). setLevel (ведение журнала.DEBUG)
Запуск этой программы с включенной регистрацией DEBUG
выглядит так:
$ ./fixrace.py
Тестирование заблокированного обновления. Начальное значение 0.
Тема 0: начало обновления
Поток 0 собирается заблокировать
Поток 0 заблокирован
Тема 1: начало обновления
Поток 1 собирается заблокировать
Поток 0 собирается снять блокировку
Тема 0 после релиза
Тема 0: завершение обновления
Поток 1 заблокирован
Поток 1 собирается снять блокировку
Тема 1 после релиза
Тема 1: завершение обновления
Тестирование заблокированного обновления.Конечное значение - 2.
В этих выходных данных вы можете видеть, что Thread 0
получает блокировку и все еще удерживает ее, когда переходит в спящий режим. Затем запускается поток 1
и пытается получить ту же блокировку. Поскольку поток Thread 0
все еще удерживает его, поток Thread 1
должен ждать. Это взаимное исключение, которое обеспечивает замок Lock
.
Многие примеры в оставшейся части этой статьи будут иметь ведение журнала уровня WARNING
и DEBUG
.Обычно мы показываем только вывод уровня WARNING
, поскольку журналы DEBUG
могут быть довольно длинными. Попробуйте программы с включенным логированием и посмотрите, что они делают.
Тупик
Прежде чем двигаться дальше, вы должны рассмотреть типичную проблему при использовании Locks
. Как вы видели, если Lock
уже был получен, второй вызов .acquire ()
будет ждать, пока поток, который удерживает Lock
, не вызовет .выпуск ()
. Как вы думаете, что происходит, когда вы запускаете этот код:
импортная резьба
l = threading.Lock ()
print ("перед первым приобретением")
l.acquire ()
print ("перед вторым приобретением")
l.acquire ()
print ("дважды полученная блокировка")
Когда программа вызывает l.acquire ()
второй раз, она зависает в ожидании снятия блокировки Lock
. В этом примере вы можете исправить тупик, удалив второй вызов, но тупиковые ситуации обычно возникают из-за одной из двух тонких вещей:
- Ошибка реализации, из-за которой блокировка
Блокировка
не разблокируется должным образом - Проблема проектирования, при которой служебная функция должна вызываться функциями, которые могут иметь или не иметь блокировку
Первая ситуация случается иногда, но использование блокировки Lock
в качестве диспетчера контекста значительно снижает частоту.По возможности рекомендуется писать код для использования диспетчеров контекста, поскольку они помогают избежать ситуаций, когда исключение пропускает вас через вызов .release ()
.
Проблема дизайна может быть немного сложнее на некоторых языках. К счастью, потоки Python имеют второй объект, называемый RLock
, который разработан как раз для этой ситуации. Он позволяет потоку несколько раз выполнять .acquire ()
и RLock
, прежде чем он вызовет .release ()
.Этот поток по-прежнему должен вызывать .release ()
столько же раз, сколько он вызывал .acquire ()
, но он все равно должен это делать.
Lock
и RLock
- два основных инструмента, используемых в резьбовом программировании для предотвращения состояний гонки. Есть несколько других, которые работают по-разному. Прежде чем вы посмотрите на них, давайте перейдем к немного другой проблемной области.
Производитель-Потребитель нарезание резьбы
Проблема "производитель-потребитель" - это стандартная задача в области компьютерных наук, используемая для рассмотрения проблем многопоточности или синхронизации процессов.Вы рассмотрите его вариант, чтобы получить некоторое представление о том, какие примитивы предоставляет модуль Python threading
.
В этом примере вы представите программу, которой нужно читать сообщения из сети и записывать их на диск. Программа не запрашивает сообщение, когда хочет. Он должен прослушивать и принимать сообщения по мере их поступления. Сообщения не будут приходить в обычном темпе, а будут приходить пакетами. Эта часть программы называется продюсером.
С другой стороны, если у вас есть сообщение, вам нужно записать его в базу данных. Доступ к базе данных медленный, но достаточно быстрый, чтобы не отставать от среднего темпа сообщений. Это , а не достаточно быстро, чтобы успевать за пакетом сообщений. Эта часть является потребителем.
Между производителем и потребителем вы создадите конвейер
, который будет изменяться по мере того, как вы узнаете о различных объектах синхронизации.
Это базовая схема.Давайте посмотрим на решение, использующее Lock
. Он не работает идеально, но в нем используются инструменты, которые вы уже знаете, так что это хорошее место для начала.
Производитель-Потребитель, использующий замок
Поскольку это статья о Python threading
, и поскольку вы только что прочитали о примитиве Lock
, давайте попробуем решить эту проблему с двумя потоками, используя Lock
или два.
Общая схема такова, что существует поток производителя
, который читает из поддельной сети и помещает сообщение в конвейер
:
импорт случайный
SENTINEL = объект ()
def производитель (конвейер):
"" "Представьте, что мы получаем сообщение из сети."" "
для индекса в диапазоне (10):
сообщение = random.randint (1, 101)
logging.info ("Производитель получил сообщение:% s", сообщение)
pipeline.set_message (сообщение, "Производитель")
# Отправьте дозорное сообщение, чтобы сообщить потребителю, что мы закончили
pipeline.set_message (SENTINEL, "Производитель")
Чтобы сгенерировать поддельное сообщение, производитель
получает случайное число от единицы до ста. Он вызывает .set_message ()
на конвейере
, чтобы отправить его потребителю
.
Производитель
также использует значение SENTINEL
, чтобы сигнализировать потребителю о необходимости остановиться после того, как он отправил десять значений. Это немного неудобно, но не волнуйтесь, вы увидите способы избавиться от этого значения SENTINEL
после того, как проработаете этот пример.
По другую сторону трубопровода
- потребитель:
def потребитель (конвейер):
"" "Представьте, что мы сохраняем число в базе данных." ""
сообщение = 0
пока сообщение не SENTINEL:
сообщение = конвейер.get_message ("Потребитель")
если сообщение не SENTINEL:
logging.info ("Сообщение, хранящееся у потребителя:% s", сообщение)
Потребитель
считывает сообщение из конвейера
и записывает его в поддельную базу данных, которая в данном случае просто выводит его на дисплей. Если он получает значение SENTINEL
, он возвращается из функции, которая завершает поток.
Прежде чем вы посмотрите на действительно интересную часть, конвейер
, вот раздел __main__
, который порождает следующие потоки:
, если __name__ == "__main__":
format = "% (asctime) s:% (сообщение) s"
протоколирование.basicConfig (формат = формат, уровень = logging.INFO,
datefmt = "% H:% M:% S")
# logging.getLogger (). setLevel (logging.DEBUG)
pipeline = Трубопровод ()
с concurrent.futures.ThreadPoolExecutor (max_workers = 2) в качестве исполнителя:
executeor.submit (продюсер, конвейер)
executeor.submit (потребитель, конвейер)
Это должно выглядеть довольно знакомо, поскольку оно близко к коду __main__
в предыдущих примерах.
Помните, что вы можете включить ведение журнала DEBUG
, чтобы увидеть все сообщения журнала, раскомментировав эту строку:
# лог.getLogger (). setLevel (ведение журнала.DEBUG)
Может быть целесообразно просмотреть сообщения журнала DEBUG
, чтобы точно увидеть, где каждый поток получает и снимает блокировки.
Теперь давайте взглянем на конвейер
, который передает сообщения от производителя
потребителю
:
класс Трубопровод:
"" "
Класс, обеспечивающий единый конвейер между производителем и потребителем.
"" "
def __init __ (сам):
себя.сообщение = 0
self.producer_lock = threading.Lock ()
self.consumer_lock = threading.Lock ()
self.consumer_lock.acquire ()
def get_message (я, имя):
logging.debug ("% s: собирается получить блокировку", имя)
self.consumer_lock.acquire ()
logging.debug ("% s: есть getlock", имя)
message = self.message
logging.debug ("% s: собирается выпустить сетлок", имя)
self.producer_lock.release ()
logging.debug ("% s: setlock выпущен", имя)
ответное сообщение
def set_message (я, сообщение, имя):
протоколирование.debug ("% s: собирается получить setlock", имя)
self.producer_lock.acquire ()
logging.debug ("% s: есть setlock", имя)
self.message = сообщение
logging.debug ("% s: собирается выпустить блокировку", имя)
self.consumer_lock.release ()
logging.debug ("% s: getlock выпущен", имя)
Вау! Это много кода. Довольно большой процент из них - это просто записи в журнал, чтобы было легче увидеть, что происходит, когда вы их запускаете. Вот тот же код со всеми удаленными операторами журнала:
класс Трубопровод:
"" "
Класс, обеспечивающий единый конвейер между производителем и потребителем."" "
def __init __ (сам):
self.message = 0
self.producer_lock = threading.Lock ()
self.consumer_lock = threading.Lock ()
self.consumer_lock.acquire ()
def get_message (я, имя):
self.consumer_lock.acquire ()
message = self.message
self.producer_lock.release ()
ответное сообщение
def set_message (я, сообщение, имя):
self.producer_lock.acquire ()
self.message = сообщение
self.consumer_lock.release ()
Это кажется немного более управляемым.Конвейер
в этой версии вашего кода состоит из трех членов:
-
.message
сохраняет передаваемое сообщение. -
.producer_lock
- это объектthreading.Lock
, который ограничивает доступ к сообщению для потока производителя.
-
.consumer_lock
также является потоком. Блокировка
, которая ограничивает доступ к сообщению потоком потребителя .
__init __ ()
инициализирует эти три члена, а затем вызывает .acquire ()
на .consumer_lock
. Это состояние, в котором вы хотите начать. Производителю
разрешено добавлять новое сообщение, но потребителю необходимо дождаться появления сообщения.
.get_message ()
и .set_messages ()
почти противоположны. .get_message ()
вызывает .acquire ()
на consumer_lock
.Это вызов, который заставит потребителя
ждать, пока сообщение не будет готово.
Как только потребитель
получил .consumer_lock
, он копирует значение из .message
и затем вызывает .release ()
на .producer_lock
. Снятие этой блокировки позволяет производителю
вставить следующее сообщение в конвейер
.
Прежде чем перейти к .set_message ()
, в происходит кое-что неуловимое.get_message ()
, который довольно легко пропустить. Может показаться заманчивым избавиться от сообщения
и просто завершить функцию с помощью return self.message
. Посмотрите, сможете ли вы понять, почему вы не хотите этого делать, прежде чем двигаться дальше.
Вот ответ. Как только потребитель
вызывает .producer_lock.release ()
, его можно заменить, и производитель
может начать работу. Это могло произойти до того, как .release ()
вернется! Это означает, что существует небольшая вероятность того, что, когда функция вернет self.сообщение
, которое на самом деле может быть следующим сгенерированным сообщением , поэтому вы потеряете первое сообщение. Это еще один пример состояния гонки.
Переходя к .set_message ()
, вы можете увидеть противоположную сторону транзакции. Производитель
позвонит по этому номеру с сообщением. Он получит .producer_lock
, установит .message
и вызовет .release ()
на consumer_lock
, что позволит пользователю consumer_lock
прочитать это значение.
Давайте запустим код, в котором для ведения журнала установлено значение WARNING
, и посмотрим, как он выглядит:
$ ./prodcom_lock.py
Производитель получил данные 43
Производитель получил данные 45
Потребительские данные хранения: 43
Производитель получил данные 86
Потребительские данные хранения: 45
Производитель получил данные 40
Потребительские хранилища данных: 86
Производитель получил данные 62
Потребительские данные хранения: 40
Производитель получил данные 15
Потребительские данные хранения: 62
Производитель получил данные 16
Потребительские данные хранения: 15
Производитель получил данные 61
Потребительские данные хранения: 16
Производитель получил данные 73
Потребительские хранилища данных: 61
Производитель получил данные 22
Потребительские данные хранения: 73
Потребительские данные хранения: 22
Сначала вам может показаться странным, что производитель получает два сообщения до того, как потребитель запустится.Если вы посмотрите на производителя
и .set_message ()
, вы заметите, что единственное место, где он будет ждать блокировки Lock
, - это когда он пытается поместить сообщение в конвейер. Это делается после того, как производитель
получает сообщение и регистрирует его.
Когда производитель
пытается отправить это второе сообщение, он вызывает .set_message ()
второй раз и блокируется.
Операционная система может поменять местами потоки в любое время, но обычно позволяет каждому потоку иметь разумное количество времени для выполнения перед его заменой.Вот почему производитель
обычно работает до тех пор, пока не заблокирует второй вызов .set_message ()
.
Однако, как только поток заблокирован, операционная система всегда заменяет его и находит другой поток для запуска. В этом случае единственный другой поток, которому нужно что-то делать, - это потребитель
.
Потребитель
вызывает .get_message ()
, который читает сообщение и вызывает .release ()
на .producer_lock
, тем самым позволяя производителю
снова запускаться при следующей замене потоков.
Обратите внимание, что первое сообщение было 43
, и это именно то, что прочитал потребитель
, хотя производитель
уже сгенерировал сообщение 45
.
Хотя он работает для этого ограниченного теста, это не лучшее решение проблемы производитель-потребитель в целом, потому что он допускает только одно значение в конвейере за раз. Когда производитель
получит пачку сообщений, ему некуда будет их разместить.
Давайте перейдем к лучшему способу решения этой проблемы, используя очередь
.
Производитель-Потребитель, использующий очередь
Если вы хотите иметь возможность обрабатывать более одного значения в конвейере одновременно, вам понадобится структура данных для конвейера, которая позволяет этому числу увеличиваться и сокращаться по мере резервного копирования данных от производителя
.
Python имеет модуль очереди
, который, в свою очередь, имеет класс очереди
. Давайте изменим конвейер
, чтобы использовать очередь
вместо простой переменной, защищенной блокировкой Lock
.Вы также будете использовать другой способ остановки рабочих потоков, используя примитив, отличный от Python threading
, события
.
Начнем с события , события
. Объект threading.Event
позволяет одному потоку сигнализировать о событии
, в то время как многие другие потоки могут ожидать, когда произойдет это событие
. Ключевое использование в этом коде заключается в том, что потокам, ожидающим события, не обязательно останавливать то, что они делают, они могут просто время от времени проверять состояние Event
.
Срабатывание события может происходить по многим причинам. В этом примере основной поток просто некоторое время будет спать, а затем .set ()
it:
1if __name__ == "__main__":
2 format = "% (asctime) s:% (message) s"
3 logging.basicConfig (формат = формат, уровень = logging.INFO,
4 datefmt = "% H:% M:% S")
5 # logging.getLogger (). SetLevel (logging.DEBUG)
6
7 pipeline = Трубопровод ()
8 событие = threading.Event ()
9 с параллельными фьючерсами.ThreadPoolExecutor (max_workers = 2) в качестве исполнителя:
10 executor.submit (продюсер, конвейер, событие)
11 executor.submit (потребитель, конвейер, событие)
12
13 time.sleep (0,1)
14 logging.info («Главное: собираюсь установить событие»)
15 event.set ()
Единственные изменения здесь - это создание объекта события
в строке 6, передача события
в качестве параметра в строках 8 и 9, и последний раздел в строках с 11 по 13, которые засыпают на секунду, регистрируют сообщение, а затем позвоните по номеру .set ()
по событию.
Производитель
тоже не пришлось сильно менять:
1def производитель (конвейер, событие):
2 "" "Представьте, что мы получаем номер из сети." ""
3, пока не event.is_set ():
4 сообщение = random.randint (1, 101)
5 logging.info ("Производитель получил сообщение:% s", сообщение)
6 pipeline.set_message (сообщение, «Производитель»)
7
8 logging.info («Производитель получил событие EXIT. Выход»)
Теперь он будет зацикливаться, пока не увидит, что событие было установлено в строке 3.Он также больше не помещает значение SENTINEL
в конвейер
.
Потребитель
пришлось поменять еще немного:
Потребитель 1def (конвейер, событие):
2 "" "Представьте, что мы сохраняем число в базе данных." ""
3, пока не event.is_set () или не pipeline.empty ():
4 message = pipeline.get_message («Потребитель»)
5 logging.info (
6 "Сохранение сообщения потребителем:% s (размер очереди =% s)",
7 сообщение,
8 трубопровод.qsize (),
9)
10
11 logging.info («Потребитель получил событие EXIT. Выход»)
Хотя вам нужно вынуть код, связанный со значением SENTINEL
, вам нужно было выполнить несколько более сложное условие и
. Он не только зацикливается до тех пор, пока не будет установлено событие
, но и должен продолжать зацикливаться, пока конвейер не будет опустошен.
Проверка того, что очередь пуста до того, как потребитель закончит, предотвращает еще одну забавную проблему.Если потребитель
действительно выходит, в то время как конвейер
содержит сообщения, могут произойти две неприятные вещи. Первый заключается в том, что вы теряете эти последние сообщения, но более серьезным является то, что производитель
может быть пойман на попытке добавить сообщение в полную очередь и никогда не вернуться.
Это происходит, если событие
запускается после того, как производитель
проверил условие .is_set ()
, но до того, как он вызовет конвейер .set_message ()
.
Если это произойдет, производитель может проснуться и выйти с полностью заполненной очередью. Затем поставщик
вызовет .set_message ()
, который будет ждать, пока в очереди не появится место для нового сообщения. Потребитель
уже вышел, поэтому этого не произойдет, и производитель
не выйдет.
Остальные потребительские
должны выглядеть знакомо.
Однако конвейер
кардинально изменился:
Конвейер 1 класса (очередь.Очередь):
2 def __init __ (сам):
3 super () .__ init __ (maxsize = 10)
4
5 def get_message (я, имя):
6 logging.debug ("% s: собирается из очереди", имя)
7 значение = self.get ()
8 logging.debug ("% s: получено% d из очереди", имя, значение)
9 возвращаемое значение
10
11 def set_message (я, значение, имя):
12 logging.debug («% s: собирается добавить% d в очередь», имя, значение)
13 self.put (значение)
14 logging.debug ("% s: добавлено% d в очередь", имя, значение)
Вы можете видеть, что конвейер
является подклассом очереди .Очередь
. Очередь
имеет необязательный параметр при инициализации, чтобы указать максимальный размер очереди.
Если вы дадите положительное число для maxsize
, это ограничит очередь этим количеством элементов, в результате чего .put ()
будет блокироваться до тех пор, пока не останется менее maxsize
элементов. Если вы не укажете maxsize
, очередь будет увеличиваться до пределов памяти вашего компьютера.
.get_message ()
и .set_message ()
стал намного меньше. В основном они заключают .get ()
и .put ()
в очередь
. Вам может быть интересно, куда делся весь код блокировки, который не позволяет потокам вызывать состояния гонки.
Основные разработчики, написавшие стандартную библиотеку, знали, что очередь
часто используется в многопоточных средах, и включали весь этот код блокировки внутри самой очереди
. Очередь
является потокобезопасной.
Запуск этой программы выглядит следующим образом:
$ ./prodcom_queue.py
Продюсер получил сообщение: 32
Продюсер получил сообщение: 51
Продюсер получил сообщение: 25
Продюсер получил сообщение: 94
Продюсер получил сообщение: 29
Сообщение потребителя, сохраняющее: 32 (размер очереди = 3)
Продюсер получил сообщение: 96
Сообщение потребителя, сохраняющее: 51 (размер очереди = 3)
Продюсер получил сообщение: 6
Сообщение потребителя, сохраняющее: 25 (размер очереди = 3)
Продюсер получил сообщение: 31
[удалено много строк]
Продюсер получил сообщение: 80
Сообщение потребителя, сохраняющее: 94 (размер очереди = 6)
Продюсер получил сообщение: 33
Сообщение потребителя, сохраняющее: 20 (размер очереди = 6)
Продюсер получил сообщение: 48
Сообщение потребителя, сохраняющее: 31 (размер очереди = 6)
Продюсер получил сообщение: 52
Сообщение потребителя, сохраняющее: 98 (размер очереди = 6)
Main: собираюсь назначить событие
Продюсер получил сообщение: 13
Сообщение потребителя, сохраняющее: 59 (размер очереди = 6)
Продюсер получил событие EXIT.Выход
Сообщение потребителя, сохраняющее: 75 (размер очереди = 6)
Сообщение, хранящееся у потребителя: 97 (размер очереди = 5)
Сообщение, сохраняющее потребителя: 80 (размер очереди = 4)
Сообщение потребителя, сохраняющее: 33 (размер очереди = 3)
Сообщение потребителя, сохраняющее: 48 (размер очереди = 2)
Сообщение потребителя, сохраняющее: 52 (размер очереди = 1)
Сообщение, хранящееся у потребителя: 13 (размер очереди = 0)
Потребитель получил событие EXIT. Выход
Если вы прочитаете вывод в моем примере, то увидите, что происходит кое-что интересное. Справа вверху вы можете видеть, что производитель
должен создать пять сообщений и поместить четыре из них в очередь.Операционная система заменила его, прежде чем он смог разместить пятый.
Потребитель
затем запустил и снял первое сообщение. Он распечатал это сообщение, а также то, насколько глубока была очередь на тот момент:
Сохранение сообщения потребителя: 32 (размер очереди = 3)
Вот как вы узнаете, что пятое сообщение еще не попало в конвейер
. Очередь уменьшается до трех после удаления одного сообщения. Вы также знаете, что очередь
может содержать десять сообщений, поэтому поток производителя
не был заблокирован очередью
.Он был заменен ОС.
Примечание: Ваш результат будет другим. Ваш результат будет меняться от запуска к запуску. Это самая интересная часть работы с потоками!
Когда программа начинает завершаться, вы видите, что основной поток генерирует событие
, которое вызывает немедленный выход производителя
. У потребителя
еще есть над чем поработать, поэтому он продолжает работать, пока не очистит конвейер
.
Попробуйте поиграть с разными размерами очереди и звонками до раз.sleep ()
в производителе
или потребителе
для имитации более длительного времени доступа к сети или к диску соответственно. Даже незначительные изменения этих элементов программы сильно повлияют на ваши результаты.
Это гораздо лучшее решение проблемы производителя и потребителя, но вы можете упростить его еще больше. Pipeline
действительно не нужен для решения этой проблемы. После того, как вы уберете ведение журнала, он просто превратится в очередь . Очередь
.
Вот как выглядит окончательный код с использованием очереди .Очередь
напрямую:
импорт одновременных фьючерсов
импорт журнала
очередь импорта
случайный импорт
импорт потоковой передачи
время импорта
производитель def (очередь, событие):
"" "Представьте, что мы получаем номер из сети." ""
пока не event.is_set ():
сообщение = random.randint (1, 101)
logging.info ("Производитель получил сообщение:% s", сообщение)
queue.put (сообщение)
logging.info ("Производитель получил событие. Выходит")
def потребитель (очередь, событие):
"" "Представьте, что мы сохраняем число в базе данных."" "
пока не event.is_set () или не queue.empty ():
сообщение = queue.get ()
logging.info (
"Сохранение сообщения потребителем:% s (size =% d)", message, queue.qsize ()
)
logging.info («Потребитель получил событие. Выходит»)
если __name__ == "__main__":
format = "% (asctime) s:% (сообщение) s"
logging.basicConfig (формат = формат, уровень = logging.INFO,
datefmt = "% H:% M:% S")
pipeline = queue.Queue (maxsize = 10)
событие = threading.Event ()
с одновременным.futures.ThreadPoolExecutor (max_workers = 2) в качестве исполнителя:
executeor.submit (продюсер, конвейер, событие)
executeor.submit (потребитель, конвейер, событие)
time.sleep (0,1)
logging.info («Главное: собираюсь установить событие»)
event.set ()
Это легче читать и показывает, как использование встроенных примитивов Python может упростить сложную проблему.
Lock
и Queue
- удобные классы для решения проблем параллелизма, но есть и другие классы, предоставляемые стандартной библиотекой.Прежде чем завершить это руководство, давайте кратко рассмотрим некоторые из них.
Нарезка объектов
Есть еще несколько примитивов, предлагаемых модулем Python threading
. Хотя в приведенных выше примерах они вам не понадобились, они могут пригодиться в разных сценариях использования, поэтому хорошо с ними ознакомиться.
Семафор
Первый объект Python threading
, на который нужно обратить внимание, - это threading. Семафор
- это счетчик с несколькими особыми свойствами.Во-первых, счет атомный. Это означает, что есть гарантия, что операционная система не заменит поток во время увеличения или уменьшения счетчика.
Внутренний счетчик увеличивается при вызове .release ()
и уменьшается при вызове .acquire ()
.
Следующее особое свойство заключается в том, что если поток вызывает .acquire ()
, когда счетчик равен нулю, этот поток будет блокироваться до тех пор, пока другой поток не вызовет .release ()
и увеличивает счетчик до единицы.
Семафоры часто используются для защиты ресурса с ограниченной емкостью. Например, у вас есть пул подключений и вы хотите ограничить размер этого пула определенным числом.
Таймер
threading.Timer
- это способ запланировать вызов функции по прошествии определенного времени. Вы создаете таймер
, передав количество секунд ожидания и функцию для вызова:
t = нарезание резьбы.Таймер (30.0, my_function)
Вы запускаете таймер
, вызывая .start ()
. Функция будет вызываться в новом потоке в какой-то момент после указанного времени, но имейте в виду, что нет никаких обещаний, что она будет вызвана точно в то время, которое вы хотите.
Если вы хотите остановить таймер Timer
, который вы уже запустили, вы можете отменить его, вызвав .cancel ()
. Вызов .cancel ()
после срабатывания таймера Timer
ничего не делает и не вызывает исключения.
Таймер
может использоваться для подсказки пользователю действия по прошествии определенного времени. Если пользователь выполняет действие до истечения таймера Timer
, можно вызвать .cancel ()
.
Барьер
A threading. Barrier
может использоваться для синхронизации фиксированного количества потоков. При создании Barrier
вызывающий должен указать, сколько потоков будет синхронизироваться на нем. Каждый поток вызывает .wait ()
на Barrier
.Все они будут оставаться заблокированными до тех пор, пока не будет ожидать указанное количество потоков, а затем все они будут освобождены одновременно.
Помните, что потоки планируются операционной системой, поэтому, даже если все потоки освобождаются одновременно, они будут запускаться по одному за раз.
Одно из применений Barrier
- позволить пулу потоков инициализировать себя. Если потоки ждут на барьере Barrier
после их инициализации, это гарантирует, что ни один из потоков не запустится до того, как все потоки завершат свою инициализацию.
Заключение: многопоточность в Python
Теперь вы многое узнали из того, что может предложить Python threading
, а также некоторые примеры того, как создавать многопоточные программы и проблемы, которые они решают. Вы также видели несколько примеров проблем, возникающих при написании и отладке многопоточных программ.
Если вы хотите изучить другие варианты параллелизма в Python, ознакомьтесь с «Ускорьте свою программу Python с помощью параллелизма».
Если вы хотите подробно изучить модуль asyncio
, прочтите «Асинхронный ввод-вывод в Python: полное руководство».
Что бы вы ни делали, теперь у вас есть информация и уверенность, необходимые для написания программ с использованием потоковой передачи Python!
Особая благодарность читателю Дж. Л. Диазу за помощь в наведении порядка во введении.
Пройдите тест: Проверьте свои знания с помощью нашей интерактивной викторины «Python Threading». По завершении вы получите оценку, чтобы вы могли отслеживать свой прогресс в обучении с течением времени:
Пройти тест »
Смотреть сейчас В этом руководстве есть связанный видеокурс, созданный командой Real Python.Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Threading in Python
Как работает многопоточность? Смотрите внутри!
Вы когда-нибудь задумывались, как работает многопоточность? При восковой эпиляции воск прилипает к волоскам и вырывает их (иногда он вырывает и кожу, так что в этом нет никакой ракетной науки), а во время выщипывания волосы выдергиваются достаточно просто. Бритье разрезает волосы.
Но как насчет потоков?
Как же на Земле скрученная нить выдергивает волоски?
Если вы когда-либо пробовали этот процесс самостоятельно или смотрели учебные пособия, возможно, вы имеете хорошее представление о том, как работает многопоточность.Сам метод может варьироваться в зависимости от подготовки и предпочтений косметолога, но общая концепция остается той же.
Сначала
При заправке ниток одна нить закручивается и скручивается, поэтому часть в середине петли скручивается вместе, оставляя две «ручки», которые косметолог может держать. Вот эта скрученная нить используется для выдергивания волосков. При регулировке каждой «ручки» скрученная нить может двигаться в одну или другую сторону. Это обеспечивает контроль направления, потому что волосы нужно удалять в соответствии с их структурой, то есть направлением, в котором они растут.
Во-вторых
Теперь, когда волос оказывается зажат между раскрытыми "руками" петли и медленно попадает в скрученную часть, что происходит? Прядь волос направляется в скрученную нить и скручивается вместе с ней, и этим действием она вырывается. Так что иногда можно увидеть, как косметолог немного поиграет с нитью, чтобы избавиться от запутавшихся в ней волосков.
Наконец
Звучит больно? Это. Без боли нет красоты. Однако опытный косметолог может свести к минимуму боль, работая быстро и эффективно.Одним из основных факторов, которые могут повлиять на уровень боли, является место наложения нити. Чем ближе он к корню, тем меньше растягивается прядь - и тем меньше она болит.