Что такое модель памяти python

Как работает память в Python

Хочешь знать больше о Python?

Подпишись на наш канал о Python в Telegram!

Python многое делает за нас. Мы привыкли не заботиться об управлении памятью и о написании соответствующего кода. Пусть эти процессы и скрыты, но без их понимания трудно подготовить производительный код для высоконагруженных задач. Сайт proglib.io опубликовал статью, в которой рассматривается модель памяти Python и то, как интерпретатор Python взаимодействует с оперативной памятью компьютера.

Диспетчер памяти: «командовать парадом буду я»

Python — это интерпретируемый язык программирования, поэтому перед запуском программы код на языке Python компилируется в машиночитаемые инструкции — байт-код. Инструкции байт-кода интерпретируются виртуальной машиной, определяемой реализацией языка, например, стандартной — CPython.

Оговоримся, что CPython не взаимодействует напрямую с регистрами и ячейками физической памяти — только с ее виртуальным представлением. В начале выполнения программы операционная система создает новый процесс и выделяет под него ресурсы. Выделенную виртуальную память интерпретатор использует для 1) собственной корректной работы, 2) стека вызываемых функций и их аргументов и 3) хранилища данных, представленного в виде кучи.

В отличие от C/C++, мы не можем управлять состоянием кучи напрямую из Python. Функции низкоуровневой работы с памятью предоставляются Python/C API, но обычно интерпретатор просто обращается к хранилищу данных через диспетчер памяти Python (memory manager).

Диспетчер памяти — своеобразный портье, который регистрирует и расселяет гостей отеля. Каждый постоялец получает ключ с номером комнаты, так что ни один из гостей не может заселиться не в свой номер. Две программы не могут одновременно записать переменную в одно место виртуальной памяти.

Фактически за это отвечает даже не диспетчер задач, который ожидает гостей за регистрационной стойкой, а GIL — глобальная блокировка интерпретатора. GIL гарантирует: в один и тот же момент времени байт-код выполняется только одним потоком. Главное преимущество — безопасная работа с памятью, а основной недостаток в том, что многопоточное выполнение программ Python требует специфических решений.

Очевидно, программа не сама выполняет сохранение и освобождение памяти — ведь мы не пишем соответствующих инструкций. Интерпретатор лишь запрашивает диспетчер памяти сделать это. А диспетчер уже делегирует работу, связанную с хранением данных, аллокаторам — распределителям памяти.

Организация доступной виртуальной памяти

Непосредственно с оперативной памятью взаимодействует распределитель сырой памяти (raw memory allocator). Поверх него работают аллокаторы, реализующие стратегии управления памятью, специфичные для отдельных типов объектов. Объекты разных типов — например, числа и строки — занимают разный объем, к ним применяются разные механизмы хранения и освобождения памяти. Аллокаторы стараются не занимать лишнюю память до тех пор, пока она не станет совершенно необходимой — этот момент определен стратегией распределения памяти CPython.

Python использует динамическую стратегию, то есть распределение памяти выполняется во время выполнения программы. Виртуальная память Python представляет иерархическую структуру, оптимизированную под объекты Python размером менее 256 Кб:

Блок содержит не более одного объекта Python и находится в одном из трех состояний:

Арена

Информацию о текущем распределении памяти в аренах, пулах и блоках можно посмотреть, запустив функцию sys._debugmallocstats() :

Чтобы не произошло утечки памяти, диспетчер памяти должен отследить, что вся выделенная память освободится после завершения работы программы. То есть при завершении программы CPython дает задание очистить все арены.

Освобождение памяти: счетчик ссылок, сборщик мусора

Для освобождения памяти используются два механизма: счетчик ссылок и сборщик мусора.

Всё в Python является объектами, а прародителем всех типов объектов в реализации CPython является PyObject. От него наследуются все остальные типы. В PyObject определены счетчик ссылок и указатель на фактический тип объекта. Счетчик ссылок увеличивается на единицу, когда мы создаем что-то, что обращается к объекту, например, сохраняем объект в новой переменной. И наоборот, счетчик уменьшается на единицу, когда мы перестаем ссылаться на объект.

Однако счетчик ссылок неспособен отследить ситуации с циклическими ссылками. К примеру, возможна ситуация, когда два объекта ссылаются друг на друга, но оба уже не используются программой. Для борьбы с такими зависимостями используется сборщик мусора ( garbage collector ).

Если счетчик ссылок является свойством объекта, то сборщик мусора — механизм, который запускается на основе эвристик. Задача этих эвристик — снизить частоту и объем очищаемых данных. Основная стратегия заключается в разделении объектов на поколения: чем больше сборок мусора пережил объект, тем он значимее для выполнения работы программы. Сборщик мусора имеет интерфейс в виде модуля gc.

Заключение

Сохранение и освобождение блоков памяти требует времени и вычислительных ресурсов. Чем меньше блоков задействовано, тем выше скорость работы программы. Позволим себе дать несколько советов, касающихся экономной работы с памятью:

На ранних этапах работы с кодом Python можно вполне обойтись стандартными средствами Python. Но по мере разрастания кодовой базы и приближения к коммерческому использованию кода производительность становится одним из ключевых факторов. Многие задачи, связанные с производительностью Python, лежат в области понимания устройства инструмента и конкретной реализации языка. Не пожалейте времени на изучение исходного кода CPython, находящегося в свободном доступе в репозитории на GitHub.

Источник

🐍 Помнить всё. Как работает память в Python

Leo Matyushkin

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

Диспетчер памяти: «командовать парадом буду я»

Диспетчер памяти — своеобразный портье, который регистрирует и расселяет гостей отеля. Каждый постоялец получает ключ с номером комнаты, так что ни один из гостей не может заселиться не в свой номер. Две программы не могут одновременно записать переменную в одно место виртуальной памяти.

Фактически за это отвечает даже не диспетчер задач, который ожидает гостей за регистрационной стойкой, а GIL — глобальная блокировка интерпретатора. GIL гарантирует: в один и тот же момент времени байт-код выполняется только одним потоком. Главное преимущество — безопасная работа с памятью, а основной недостаток в том, что многопоточное выполнение программ Python требует специфических решений.

Очевидно, программа не сама выполняет сохранение и освобождение памяти — ведь мы не пишем соответствующих инструкций. Интерпретатор лишь запрашивает диспетчер памяти сделать это. А диспетчер уже делегирует работу, связанную с хранением данных, аллокаторам — распределителям памяти.

Читайте также:  ашан атак что это

Организация доступной виртуальной памяти

Непосредственно с оперативной памятью взаимодействует распределитель сырой памяти (raw memory allocator). Поверх него работают аллокаторы, реализующие стратегии управления памятью, специфичные для отдельных типов объектов. Объекты разных типов — например, числа и строки — занимают разный объем, к ним применяются разные механизмы хранения и освобождения памяти. Аллокаторы стараются не занимать лишнюю память до тех пор, пока она не станет совершенно необходимой — этот момент определен стратегией распределения памяти CPython.

Python использует динамическую стратегию, то есть распределение памяти выполняется во время выполнения программы. Виртуальная память Python представляет иерархическую структуру, оптимизированную под объекты Python размером менее 256 Кб:

Блок содержит не более одного объекта Python и находится в одном из трех состояний:

Арена

Информацию о текущем распределении памяти в аренах, пулах и блоках можно посмотреть, запустив функцию sys._debugmallocstats() :

Чтобы не произошло утечки памяти, диспетчер памяти должен отследить, что вся выделенная память освободится после завершения работы программы. То есть при завершении программы CPython дает задание очистить все арены.

Освобождение памяти: счетчик ссылок, сборщик мусора

Для освобождения памяти используются два механизма: счетчик ссылок и сборщик мусора.

Однако счетчик ссылок неспособен отследить ситуации с циклическими ссылками. К примеру, возможна ситуация, когда два объекта ссылаются друг на друга, но оба уже не используются программой. Для борьбы с такими зависимостями используется сборщик мусора ( garbage collector ).

Заключение

Сохранение и освобождение блоков памяти требует времени и вычислительных ресурсов. Чем меньше блоков задействовано, тем выше скорость работы программы. Позволим себе дать несколько советов, касающихся экономной работы с памятью:

На Python создают прикладные приложения, пишут тесты и бэкенд веб-приложений, автоматизируют задачи в системном администрировании, его используют в нейронных сетях и анализе больших данных. Язык можно изучить самостоятельно, но на это придется потратить немало времени. Если вы хотите быстро понять основы программирования на Python, обратите внимание на онлайн-курс «Библиотеки программиста». За 30 уроков (15 теоретических и 15 практических занятий) под руководством практикующих экспертов вы не только изучите основы синтаксиса, но и освоите две интегрированные среды разработки (PyCharm и Jupyter Notebook), работу со словарями, парсинг веб-страниц, создание ботов для Telegram и Instagram, тестирование кода и даже анализ данных. Чтобы процесс обучения стал более интересным и комфортным, студенты получат от нас обратную связь. Кураторы и преподаватели курса ответят на все вопросы по теме лекций и практических занятий.

Источник

Глава 17. Модели памяти и операции атомарных типов

Содержание

Те соображения, которые следует принимать во внимание при программировании процессов совместной обработки, а также вытекающие из них проблемы, связаны с тем как Python управляет своей памятью. Следовательно глубокое понимание того как переменные и значения хранятся и как на них осуществляются ссылки в Python не только поможет выявлять ошибки нижнего уровня, которые влекут за собой сбои в программе совместной обработки, но также помогут оптимизировать ваш параллельный код. В данной главе мы подробно рассмотрим модель памяти Python, а также её атомарные типы, в частности их места в экосистеме совместной обработки Python.

В данной главе будут рассмотрены следующие вопросы:

Модель памяти Python, её компоненты, которые сопровождают выделение памяти на различных уровнях, а также общую философию управления памятью в Python

Собственно определение атомарных операций, ту роль, которую они играют в программировании совместной обработки и как их применять в Python

Технические требования

Вот перечень предварительных требований для данной главы:

Убедитесь что на вашем компьютере уже установлен Python 3

Вам следует иметь установленными OpenCV и NumPy для вашего дистрибутива Python 3

Выгрузите необходимый репозиторий из GitHub

На протяжении данной главы мы будем работать с вложенной папкой, имеющей название Chapter17

Ознакомьтесь со следующими видеоматериалами Code in Action

Модель памяти Python

Вы можете помнить краткое обсуждение относительно методов управления памятью в Python из Главы 15, Глобальная блокировка интерпретатора. В этом разделе мы более глубоко рассмотрим модели памяти Python сопоставляя механизмы её управления по отношению с механизмами Java и C++, а также обсудим как это соотносится с имеющимися практиками программирования совместной обработки в Python.

Основные компоненты диспетчера памяти Python

Диспетчер памяти Python состоит из ряда компонентов, которые взаимодействуют с различными логическими объектами и сопровождают различные функциональные возможности. Например, один из компонентов обрабатывает выделение памяти на нижнем уровне посредством взаимодействия с имеющимся диспетчером памяти операционной системы, в которой запущен Python и он именуется распределителем сырой памяти (raw memory allocator).

На самом верхнем уровне также имеется ряд иных распределителей памяти, которые взаимодействуют с уже упомянутой частной кучей объектов и значений. Эти компоненты имеющегося диспетчера памяти Python обрабатывают выделения специфичные для объектов, которые исполняют операции с памятью, особенные для конкретных типов данных и объектов; целые значения должны обрабатываться и управляться иными распределителями, нежели те, которые управляют строками, или же словарями либо кортежами. Так как операции сохранения и считывания различаются между этими типами данных, эти распределители памяти различных специфических объектов реализуются для получения дополнительной скорости, принося в жертву некое пространство обработки.

На один шаг ниже упомянутого ранее распределителя памяти находятся распределители самой системы из стандартной библиотеки C (в предположении что вашим рассматриваемым интерпретатором Python выступает CPython). Порой именуемые как распределители общего назначения, ути написанные на C логические объекты отвечают за помощь распределителю сырой памяти при взаимодействии со своим диспетчером памяти операционной системы.

Полная модель описанного ранее диспетчера памяти Python может быть проиллюстрирована следующей схемой:

Рисунок 17-1

Модель памяти в виде помеченного ориентированного графа

Его часто рассматривается как ориентированный граф, так как его модель памяти отслеживает свои данные и переменные исключительно посредством указателей: значением каждой переменной является указатель, причём он моежет указывать на некий символ, какое- то число, либо некую подпрограмму. Тем самым эти указатели выступают направленными рёбрами в определённом объекте графа, а реальные значения (символы, числа, подпрограммы) являются узлами такого графа. Приводимая ниже схема является неким упрощением такой модели памяти Lisp на его ранних стадиях:

Рисунок 17-2

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

Читайте также:  арбор бройлер что за порода

Другим видом гибкости этого является то, что данная предоставляемая модель памяти являет тот факт, что на всякий объект может ссылаться любое число указателей (или неограниченное число переменных) и следовательно он может видоизменяться каждым из них. Мы уже наблюдали имеющееся воздействие этой характеристики в некоем образце программы в Главе 15, Глобальная блокировка интерпретатора, когда две переменные ссылаются на один и тот же (изменяемый) объект (что достигается тем, что одна переменная присваивается другой) и одна переменная успешно видоизменяет данный объект по своей ссылке, тогда это изменение также отражается через ссылку вашего второго объекта.

Как уже обсуждалось в Главе 15, Глобальная блокировка интерпретатора, это не походит на управление памятью в C++. Например, в случае когда некоторой переменной (которая не является указателем или ссылкой) присваивается некое конкретное значение, этот язык программирования скопирует такое определённое значение в то местоположение в памяти, которое содержит ваша первоначальная переменная. Кроме того, некая переменная присваивается иной переменной, значение из местоположения в памяти последней будет скопировано в местоположение предыдущей; никакой взаимосвязи в дальнейшем эти две переменные не поддерживают после выполненного присвоения.

Однако кое- кто утверждает, что это фактически может быть неким недостатком при программировании, в особенности при программировании совместной обработки, так как не скоординированные попытки видоизменения какого- то разделяемого объекта могут повлечь за собой нежелательные результаты. Будучи искушённым программистом Python, вы также могли заметить, что данный тип ошибки (когда некая переменная одного заданного типа ссылается на некий объект другого, несовместимого типа) достаточно распространён при программировании в Python. Это также непосредственный результат данной модели памяти, потому что, опять же, некий ссылочный указатель может указывать на что угодно.

В контексте параллельности

Имея в виду обсуждённые теоретические основы имеющейся в Python модели памяти, какое воздействие мы можем ожидать от них на общую экосистему программирования совместной обработки в Python? К счастью, имеющаяся в Python модель памяти работает в пользу параллельного программирования в том смысле, что она позволяет думать и рассуждать о совместной обработке более простым образом и намного более интуитивно понятно. В частности, Python реализует свою модель памяти и исполняет свои программы именно так, как мы обычно и ожидаем.

Для понимания этого преимущества, которым обладает Python, давайте вначале рассмотрим совместную обработку в языке программирования Java. Для достижения наилучшей производительности в смысле скорости в параллельных программах (в частности, в многопоточных программах), Java позволяет ЦПУ перестраивать тот порядок, в котором исполняются определённые, содержащиеся в коде Java, операции. Такая перестановка, однако, производится произвольным образом, поэтому мы не можем простым образом обосновать получаемый порядок просто исходя из имеющейся упорядоченной последовательности того кода, который исполняется множеством потоков. Это влечёт за собой тот факт, что если некая программа совместной обработки на Java исполняется неким не предусмотренным образом, её разработчику придётся потратить значительное время на определение текущего порядка исполнения данной программы чтобы выловить ошибку в своей программе.

Атомарные операции в Python

Другой важной темой, относящейся к управлению памятью являются атомарные операции. В данном подразделе мы исследуем само определение того что такое быть атомарным в программировании, ту роль, которую атомарные операции имеют в контексте программирования совместной обработки, а также, наконец, как применять атомарные операции при программировании на Python.

Что такое быть атомарным?

Давайте вначале исследуем реальные характеристики для того чтобы быть атомарным. Если некая операция атомарна при программировании совместной обработки, тогда она не может быть прервана никакими прочими логическими объектами из этой программы на протяжении своего исполнения; некая атомарная операция также может называться линеаризуемой, неразложимой или не прерываемой. Исходя из самой природы условий состязательности и того что обычно она присутствует в программах совместной обработки, совершенно очевидно заключить, что атомарность является желательной характеристикой для программы, так как она гарантирует необходимую целостность имеющихся совместных данных, а также защищает их от не скоординированных видоизменений.

Сам термин «атомарности» указывает на тот факт, что некая атомарная операция проявляется одномоментно для остальной части программы, в которой она содержится. Это означает, что данная операция должна быть исполнена неким слитным образом, без прерываний. Наиболее общий метод реализации атомарности, как вы вероятно могли догадаться, состоит во взаимном исключении, или блокировках. Блокировки, как мы уже видели, требуют взаимодействия с неким разделяемым ресурсом для получения его одним потоком или процессом за раз, тем самым защищая данное взаимодействие одного потока/ процесса от его прерывания и потенциального разрушения другими соперничающими потоками или процессами.

Если некий программист допускает для некоторых из своих операций быть неатомарными, им также требуется допускать для этих операций быть достаточно аккуратными и гибкими (в смысле взаимодействия и видоизменения данных) с тем, чтобы никакие ошибки не происходили в результате их прерывания иными операциями. Тем не менее, если бы имело место неправильное и ошибочное поведение при прерывании подобных операций во время их исполнения, их программисту было бы достаточно сложно в действительности воспроизвести и отладить такое поведение.

Повторное рассмотрение GIL

Одним из основных элементов в обсуждаемом контексте атомарных операций Python, безусловно, является GIL; имеются дополнительные распространённые неверные представления, а также сложности относительно той роли, которую играет GIL в атомарных операциях.

К примеру, относительно самого определения атомарных операций, кое- кто пытается утвержлать, что все операции в Python на самом деле атомарные, поскольку имеющаяся GIL на самом деле требует исполнять потоки согласованным образом, причём только один способен исполняться в каждый конкретный момент времени. По существу это ложное утверждение. То требование существующего GIL, что только один поток Python может исполнять код Python в некий определённый момент времени не влечёт за собой присутствия атомарности для всех операций Python; одна операция всё ещё может прерываться другой, причём ошибки всё ещё могут происходить по причине неверной обработки и разрушения совместных данных.

Читайте также:  swift mt900 что это

В Python 2 установленным по умолчанию значением для данной частоты является 1 000 инструкций байтового кода, что означает, что после того как поток успешно исполнить 1 000 инструкций байтового кода, имеющийся интерпретатор Python выберет из других активных потоков, которые ожидают своего исполнения. Если имеется хотя бы один ожидающий поток, управляющий интерпретатор заставит исполняемый в настоящий момент времени поток освободить имеющуюся GIL и заставит этот поток ожидать его и тем самым запускает исполнение своего следующего потока.

В Python 3 значение частоты фундаментально иное. Тот элемент, который применяется для значения частоты теперь основывается на времени, в частности, секундах. Со значением по умолчанию в 15 миллисекунд такая частота определяет, что если некий поток исполнялся по крайней мере то количество времени, эквивалентное этому пороговому значению, тогда имеет место событие переключения этого потока (совместно с высвобождением и получением имеющейся GIL) как только данный поток завершит соответствующее исполнение своей текущей инструкции байтового кода.

Врождённая атомарность в Python

Как уже упоминалось ранее, некая операция может быть прервана в процессе своего исполнения если тот поток, который её исполняет проходит предел своего исполнения (к примеру, 15 миллисекунд в Python 3 по умолчанию), причём в этот момент данная операция обязана завершить свою текущую инструкцию байтового кода и вернуть обратно имеющуюся GIL другому ожидающему потоку. Это означает, что событие переключения потока имеет место только между инструкциями байтового кода.

В Python существуют операции, которые могут исполняться в одной единственной инструкции байтового кода и тем самым атомарны по своей природе без соответствующей помощи внешних механизмов, таких как взаимное исключение. В частности, если некая операция в потоке завершает своё исполнение в одном единственном байтовом коде, она не может быть прервана соответствующим событием переключения потока, так как такое событие имеет место после завершения соответствующей инструкции байтового кода. Эта характеристика врождённой атомарности очень полезна, поскольку она позволяет тем операциям, которые обладают ею, свободно исполнять свои инструкции даже если не был применён никакой метод синхронизации, в то же время обеспечивая то, что они не будут прерваны и не получат свои данные испорченными.

Сопоставление атомарность и не атомарности

Важно отметить, что для программистов может оказаться удивительным изучать какие операции в Python являются атомарными, а какие нет. Кое- кто может предположить, что так как простые операции требуют меньше байтового кода чем сложные, такие более простые операции, скорее всего, должны быть врождённо атомарными. Тем не менее, это не имеет места, и единственный способ определить без сомнений какие операции являются атомарными врождённо состоит в выполнении дальнейшего анализа.

В соответствии с документацией Python 3 (которую можно отыскать по ссылке), некие примеры врождённой атомарности включают в себя:

Добавление некоторого предварительно определённого объекта в конец списка

Расширение некого списка другим списком

Выборка элемента из некоторого списка

«Выталкивание» (Popping) из какого- то списка

Сортировка некоего списка

Назначение переменной другой переменной

Назначение переменной какому- то атрибуту некоего объекта

Создание новой записи для какого- то словаря

Обновление некоего словаря другим словарём

А вот некоторые операции, которые не являются врождённо атомарными:

Инкрементальное увеличение некоего целого значения, в том числе при помощи +=

Обновление некоторого элемента в каком- то списке по ссылке другого элемента в этом списке

Обновление некоторого элемента в каком- то словаре по ссылке другого элемента в этом словаре

Имитация в Python

После исполнении данного сценария много раз мы можем наблюдать, что в нашем коде по- существу присутствует условие состязательности. Это иллюстрирует неверное значение сч1тчика, которое менее чем 1 000. Например, я получил в выводе следующее:

В своём файле Chapter17/example2.py мы имеем следующий код:

Поскольку наш метод list.append() является атомарной операцией, тем не менее, это гарантирует что никакого условия состязательности нет при вызове нашей функции foo() и взаимодействии с имеющимся глобальным списком. Это иллюстрирует получаемая длина данного списка по окончанию нашей программы. Не имеет значения сколько раз мы исполнили данную программу, наш список вскгда будет иметь длину 1 000:

Даже хотя некоторые операции в Python являются врождённо атомарными, может оказаться достаточно сложным сказать будет ли данная операция являться атомарной сама по себе или нет. Так как конкретное приложение не атомарных операций на совместных данных может приводить к условиям состязательности, а тем самым к ошибочным результатам, всегда рекомендуется чтобы программисты применяли механизмы синхронизации чтобы обеспечить требуемую целостность разделяемых данных в программе совместной обработки.

Выводы

В этой главе мы изучили ту структуру, которая лежит в основе модели памяти Python, а также как сам язык управляет своими значениями и переменными в контексте программирования совместной обработки. Принимая во внимание как структурировано и организовано управление памятью в Python, умозаключения о поведении некоторой программы совместной обработки может быть существенно проще чем аналогичные размышления в ином языке программирования. Такая простота понимания и отладки программ совместной обработки в Python, тем не менее, также получается с неким снижением производительности.

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

В своей следующей главе мы рассмотрим как построить некий сервер совместной обработки с нуля. На протяжении этого процесса мы дополнительно изучим реализацию протоколов взаимодействия, а также применение совместной обработки к некоторому существующему приложению Python.

Вопросы

Каковы основные компоненты диспетчера памяти Python?

Чем имеющаяся модель памяти Python походит на помеченный ориентированный граф?

В чём состоят преимущества и недостатки модели памяти Python в отношении разработки приложений совместной обработки на Python?

Что представляют из себя атомарные операции и почему они желательны в программировании совместной обработки?

Приведите три примера врождённо атомарных операций Python.

Дальнейшее чтение

Для получения дополнительных сведений вы можете воспользоваться следующими ссылками:

Источник

Информ портал о технике и не только