Операционная система Windows должна обеспечивать специальные сервисы, которые позволяют синхронизировать доступ к разделяемому ресурсу, так как без помощи операционной системы процесс или поток не узнает о том, что у него есть исключительное право (sole) доступа к ресурсу. Для того чтобы лучше понять это, представьте, что вы пишете программы для многозадачной операционной системы, которая не обеспечивает никой поддержки синхронизации. Представим далее, что у вас есть два одновременно выполняющихся потока А и В, каждому из которых время от времени нужен доступ к некоторому ресурсу R (например, к файлу на диске), способному предоставить доступ только одному потоку в каждый определенный момент времени. Для того чтобы помешать доступу к R одного потока, в то время, когда другой поток использует этот ресурс, вы пробуете реализовать следующее решение. Во-первых, вы создаете переменную, названную flag, которая инициализируется с нулевым значением и доступна обоим потокам. Далее перед использованием фрагмента кода, в котором выполняется доступ к R, вы ждете очистки переменной flag, затем устанавливаете эту переменную, обращаетесь к ресурсу R и в конце очищаете flag. Таким образом, перед доступом любого потока к ресурсу R он выполнит следующий фрагмент кода: ,rtH.le(flag) ' // ожидает очистки flag flag - 1; // устанавливает flag // доступ к ресурсу R flag = °' // очищает flag Идея, реализованная в этом коде, заключается в том, что ни один поток не получит доступа к ресурсу R, если переменная flag установлена, т. е. содержит ненулевое значение. Теоретически такой подход ведет к правильному решению, но в действительности он оставляет желать лучшего, потому что не всегда будет работать. Давайте рассмотрим почему. Приведенный код на самом деле не препятствует одновременному доступу обоих потоков к ресурсу R. Цикл while по существу выполняет повторяющиеся операторы загрузки и сравнения для переменной flag. Другими словами, он тестирует значение этой переменной. Когда она очищена, следующая строка кода присваивает 1 переменной flag. К несчастью, эти две операции могут выполниться в двух разных квантах времени, между которыми доступ к переменной flag может получить другой поток и, таким образом, тоже обзавестись разрешением на доступ к ресурсу R в то же самое время. Чтобы лучше понять, представьте, что поток А входит в цикл while и обнаруживает, что переменная flag равна нулю, т. е. дан "зеленый свет" для доступа к R. Однако прежде чем он успел установить значение flag равным 1, его квант времени закончился, и поток В возобновил выполнение. Если В выполнит свой цикл while, то также обнаружит, что переменная flag равна нулю, и предположит, что безопасно обращаться к ресурсу R. Когда же поток А возобновится, он тоже обратится к R. Решающая причина возникшей проблемы состоит в том, что проверка и установка переменной flag не включены в состав одной неразрывной операции. Более того, как только что было показано, они могут быть выполнены в разных квантах процессорного времени. Как бы вы не старались, эту проблему нельзя решить на уровне кода приложения так, чтобы получить абсолютную гарантию того, что один и только один поток имеет доступ к ресурсу в определенный Момент времени. Решение проблемы синхронизации столь же элегантно, сколь и просто. Операционная система (в данном случае Windows) предоставляет процедуру, представляющую собой одну неразрывную операцию, которая тестирует, а при необходимости и устанавливает значение переменной flag. На языке системных инженеров она называется операцией проверки и установки. Исторически сложилось так, что флаги, используемые для управления доступом к Разделяемому ресурсу и обеспечения синхронизации потоков (или процессе), называются семафорами. Семафор — сердцевина системы синхронизации операционной системы Windows.
|