Чтобы объяснить состояние гонки, сначала нужно немного понять, как компьютеры работают изнутри. Когда вы используете операционную систему, вы выполняете различные действия, такие как открытие окна терминала командной строки, запуск браузера и т. д. Каждое из этих действий приводит к тому, что операционная система создает новый поток выполнения.
Поток — это процесс вычислений. Он выполняет различные шаги (программные шаги, первоначально написанные в виде исходного кода и обычно скомпилированные компилятором), необходимые для выполнения задачи, которую вы поручили операционной системе или программному обеспечению, запущенному на ней.
В Linux такой поток уникально идентифицируется с помощью PID (идентификатора процесса).
В Windows поток также уникально идентифицируется по идентификатору процесса (см. столбец PID в Диспетчере задач Windows), хотя реализация обработки процессов отличается между Linux и Windows: различается базовый код, инструменты взаимодействия с PID и ограниченная совместимость. Кроме того, PID в Windows не следует путать с Product ID PID (тот же термин, но другое значение) или VID (Vendor ID). Последние два относятся к идентификации устройств и не связаны с управлением процессами.
Когда поток запускается, он может создавать другие потоки. Исходный поток часто называют основным или родительским потоком. Например, когда вы нажимаете на значок вашего любимого браузера, он сразу запускает поток (основной поток), который очень быстро создает несколько дочерних потоков, таким образом становясь родительским.
Потоки можно сравнить с бегунами в гонке. Например, представьте загруженный сервер базы данных, обслуживающий множество подключенных клиентов. Каждый из этих клиентских потоков (заметьте использование слова "поток") в большинстве случаев будет иметь хотя бы один поток на сервере базы данных и/или внутри самого программного обеспечения базы данных (то есть два потока: один в операционной системе и один в ПО базы данных).
Сервер базы данных пытается обслужить все эти потоки одновременно — отсюда и термин "конкурентные процессы" или "конкурентные потоки", и если в программном обеспечении базы данных (или операционной системе и т.д.) есть ошибки, то рано или поздно может возникнуть состояние гонки.
Что такое состояние гонки?
Простой способ объяснить это на примере бегунов — представить финишную черту, где два бегуна пересекают ее в одно и то же время. Для людей это маловероятно, но для компьютеров, обрабатывающих тысячи операций за миллисекунду, это более реалистично.
Другой пример — эстафетная гонка, где бегуны передают эстафетную палочку друг другу. Представьте, что один из участников ошибся, и теперь два бегуна думают, что должны получить красную палочку.
В эстафете передача палочки — это важный момент, так как предыдущий бегун может остановиться, а новому нужно бежать дальше. Теперь два бегуна пытаются схватить палочку — это будет интересно смотреть, но ясно, что это приведет к сбоям.
По сути, состояние гонки — это ошибка или дефект в коде компьютерной системы, который приводит к непредсказуемым результатам: неожиданной последовательности событий. Обычно это вызвано конфликтом двух потоков, хотя в реальном конфликте могут быть задействованы более двух потоков, и чаще всего в сбое участвует больше двух потоков.
В примере с людьми у нас было два человека, пытающихся получить доступ к объекту одновременно, что привело к повреждению (термин в компьютерах, означающий, что данные были испорчены, такие как память, данные на диске или в процессоре). В момент, когда двое людей (или потоков) пытались схватить палочку, возник конфликт. В компьютерных терминах два потока попытались записать в одно и то же место в памяти, что должно было быть доступно только одному потоку.
Состояния гонки могут возникать в разных областях, например, внутри электроники, в программном обеспечении и в повседневной жизни. Например, "столкновение вызова" — это телекоммуникационный термин, описывающий ситуацию, когда канал связи захвачен с обоих концов одновременно. В программном обеспечении — одной из самых значимых областей возникновения состояний гонки — существует множество возможных видов таких ситуаций.
Еще один пример состояния гонки в программном обеспечении: представьте два потока, работающих с одной и той же областью памяти. Пользователь только что отправил форму, и программное обеспечение на сервере записывает эту форму в память. В то же время другой пользователь считывает поля этой формы из той же области памяти. В зависимости от того, как это происходит, второй пользователь может получить частично неправильную форму с частично обновленной информацией.
Предотвращение состояний гонки: безопасность потоков
В IT-индустрии ведется много дискуссий о состояниях гонки. В зависимости от языка программирования, который вы используете, могут быть различные механизмы для обработки состояний гонки. Часто используется термин "безопасность потоков" или "потокобезопасное приложение". Эти термины указывают на то, что код или программное обеспечение написаны таким образом, чтобы избежать состояний гонки.
Если программное обеспечение считается потокобезопасным, это означает, что оно не подвержено состояниям гонки. Однако, в большинстве случаев "считается" потокобезопасным — это лучшее, чего могут добиться разработчики, особенно если существует множество потоков и взаимодействий между ними. Сложность многих потоков, работающих с множеством ресурсов, может превратиться в огромное количество возможных состояний гонки.
Существуют различные программные конструкции, которые помогают предотвратить состояния гонки, например, семафоры и мьютексы. Их сложность зависит от языка программирования и поддержки многопоточной работы. Например, в C++ есть класс std::mutex для реализации мьютекса (т.е. блокировки "взаимного исключения"). Однако, в Bash такой конструкции нет.
Более того, можно изучить, какие конструкции, функции или библиотеки уже являются потокобезопасными, и использовать их как основу для создания новых функций или программного обеспечения.
Реализация даже базовых потокобезопасных конструкций может быть сложной задачей. Например, рассмотрим трудности реализации семафора в Bash.
Заключение
В этой статье мы рассмотрели вычислительные потоки и состояния гонки. Мы использовали аналогии с беговыми гонками и эстафетами, чтобы объяснить базовые состояния гонки, которые могут возникнуть в компьютерах. Наконец, мы обсудили безопасность потоков, различные реализации обработки состояний гонки в языках программирования и способы их предотвращения.