Введение
В вычислительной технике, ограничение доступа к данным одной нитью за раз – это концепция, используемая в логике и теории вероятностей. В информатике взаимное исключение является свойством управления конкурентным доступом, которое применяется для предотвращения гонок данных. Оно заключается в требовании, чтобы один поток выполнения никогда не входил в критическую секцию, пока другой поток выполнения уже получает к ней доступ. Критическая секция – это интервал времени, в течение которого поток выполнения обращается к общему ресурсу или общей памяти. Общий ресурс – это объект данных, который пытаются изменить два или более конкурирующих потока (при этом разрешены две одновременные операции чтения, но не разрешены две одновременные операции записи или одна операция чтения и одна операция записи, поскольку это приводит к несогласованности данных). Алгоритмы взаимного исключения гарантируют, что если процесс уже выполняет операцию записи в объект данных [критическую секцию], то ни одному другому процессу/потоку не разрешается получать доступ к этому объекту или изменять его, пока первый процесс не завершит запись в объект данных [критическую секцию] и не освободит объект для чтения и записи другими процессами. Требование взаимного исключения впервые было определено и решено Эдсгером В. Дейкстрой в его основополагающей статье 1965 года «Решение проблемы управления конкурентным программированием», которая считается первой темой в изучении конкурентных алгоритмов. Простой пример важности взаимного исключения на практике можно представить с помощью односвязного списка из четырех элементов, из которого необходимо удалить второй и третий элементы. Удаление узла, находящегося между двумя другими узлами, выполняется путем изменения указателя `next` предыдущего узла, чтобы он указывал на следующий узел (то есть, если удаляется узел `i`, то указатель `next` узла `i-1` изменяется, чтобы он указывал на узел `i+1`, тем самым удаляя из списка любую ссылку на узел `i`). Если такой список используется совместно несколькими потоками выполнения, два потока могут одновременно попытаться удалить два разных узла: один поток изменяет указатель `next` узла `i-1`, чтобы он указывал на узел `i+1`, а другой поток изменяет указатель `next` узла `i`, чтобы он указывал на узел `i+2`. Хотя обе операции удаления завершаются успешно, желаемое состояние списка не достигается: узел `i+1` остается в списке, поскольку указатель `next` узла `i-1` указывает на узел `i+1`. Эту проблему (называемую гонкой данных) можно избежать, используя требование взаимного исключения, чтобы гарантировать, что одновременные обновления одной и той же части списка не могут произойти. Термин «взаимное исключение» также используется для обозначения одновременной записи в адрес памяти одним потоком, в то время как к этому же адресу памяти обращаются (манипулируют или читают) один или несколько других потоков.
the concept in logic and probability theory
In computer science, mutual exclusion is a property of concurrency control, which is instituted for the purpose of preventing race conditions. It is the requirement that one thread of execution never enters a critical section while a concurrent thread of execution is already accessing said critical section, which refers to an interval of time during which a thread of execution accesses a shared resource or shared memory. The shared resource is a data object, which two or more concurrent threads are trying to modify (where two concurrent read operations are permitted but, no two concurrent write operations or one read and one write are permitted, since it leads to data inconsistency). Mutual exclusion algorithms ensure that if a process is already performing write operation on a data object [critical section] no other process/thread is allowed to access/modify the same object until the first process has finished writing upon the data object [critical section] and released the object for other processes to read and write upon. The requirement of mutual exclusion was first identified and solved by Edsger W. Dijkstra in his seminal 1965 paper "Solution of a problem in concurrent programming control", which is credited as the first topic in the study of concurrent algorithms. A simple example of why mutual exclusion is important in practice can be visualized using a singly linked list of four items, where the second and third are to be removed. The removal of a node that sits between two other nodes is performed by changing the next pointer of the previous node to point to the next node (in other words, if node i is being removed, then the next pointer of node i – 1 is changed to point to node i + 1, thereby removing from the linked list any reference to node i). When such a linked list is being shared between multiple threads of execution, two threads of execution may attempt to remove two different nodes simultaneously, one thread of execution changing the next pointer of node i – 1 to point to node i + 1, while another thread of execution changes the next pointer of node i to point to node i + 2. Although both removal operations complete successfully, the desired state of the linked list is not achieved: node i + 1 remains in the list, because the next pointer of node i – 1 points to node i + 1. This problem (called a race condition) can be avoided by using the requirement of mutual exclusion to ensure that simultaneous updates to the same part of the list cannot occur. The term mutual exclusion is also used in reference to the simultaneous writing of a memory address by one thread while the aforementioned memory address is being manipulated or read by one or more other threads.
Аппаратные решения
На системах с одним процессором, самым простым способом достижения взаимного исключения является отключение прерываний во время критической секции процесса. Это предотвратит выполнение любых обработчиков прерываний (фактически предотвращая вытеснение процесса). Хотя это решение эффективно, оно приводит ко многим проблемам. Если критическая секция занимает много времени, системные часы будут сбиваться каждый раз при выполнении критической секции, поскольку прерывание таймера не обслуживается, что делает отслеживание времени в критической секции невозможным. Кроме того, если процесс остановится во время критической секции, управление не вернется другому процессу, что фактически приведет к остановке всей системы. Более изящный способ достижения взаимного исключения – это занятое ожидание. Занятое ожидание эффективно как для однопроцессорных, так и для многопроцессорных систем. Взаимное исключение обеспечивается использованием общей памяти и атомарной инструкции "проверка и установка". Процесс может выполнить проверку и установку в ячейке общей памяти, и поскольку операция атомарна, только один процесс может установить флаг одновременно. Любой процесс, которому не удалось установить флаг, может либо перейти к выполнению других задач и попробовать снова позже, либо освободить процессор для другого процесса и попробовать снова позже, либо продолжать циклически проверять флаг, пока не сможет его получить. Вытеснение все еще возможно, поэтому этот метод позволяет системе продолжать функционировать, даже если процесс останавливается, удерживая блокировку. Для обеспечения взаимного исключения структур данных можно использовать несколько других атомарных операций, наиболее заметной из которых является "сравнить и заменить" (CAS). CAS можно использовать для достижения взаимоисключения без ожидания для любой общей структуры данных, создав связанный список, в котором каждый узел представляет желаемую операцию. Затем CAS используется для изменения указателей в связанном списке при вставке нового узла. Только один процесс может успешно выполнить CAS; все остальные процессы, пытающиеся добавить узел одновременно, должны будут повторить попытку. Каждый процесс может хранить локальную копию структуры данных и, проходя по связанному списку, выполнять каждую операцию из списка на своей локальной копии.
Ограниченная проблемой взаимного исключения
Один бинарный регистр "test&set" достаточен для обеспечения решения проблемы взаимного исключения, свободного от взаимных блокировок. Однако решение, построенное с использованием регистра "test&set", может привести к голоданию некоторых процессов, которые окажутся в бесконечном цикле попыток.
Восстанавливаемое взаимное исключение
Большинство алгоритмов взаимного исключения разработаны с предположением, что во время выполнения процесса в критическом разделе не произойдет сбоев. Однако в реальности такие сбои могут возникать довольно часто. Например, внезапное отключение электроэнергии или неисправное соединение могут привести к тому, что процесс, находящийся в критическом разделе, столкнется с необратимой ошибкой или окажется неспособным продолжить работу. Если произойдет подобный сбой, традиционные алгоритмы взаимного исключения, не устойчивые к сбоям, могут привести к взаимной блокировке или нарушить другие важные свойства живости. Для решения этой проблемы было предложено несколько решений, использующих механизмы восстановления после сбоев.