Синхронизация потоков в операционной системе Windows, страница 16

Предотвращение взаимного блокирования потоков

Разрабатывая приложения для многозадачной или многопоточной среды, вы можете столкнуться с возможностью возникновения взаимного блокирования потоков (по-английски это понятие обозначается терминами deadlock — тупик или deadly embrace — смертельные объятия). Проще говоря, взаимное блокирование потоков — это состояние, когда два потока не могут продолжить выполнение, так как каждый из них ожидает, пока второй освободит занятый им ресурс.

Предположим, например, что каждый из двух потоков для продолжения работы должен завладеть двумя мьютексами (МТХА и МТХВ). Предположим, что в определенный момент времени первый поток завладел мьютексом МТХА и собирается завладеть мьютексом МТХВ. Но в этот же момент второй поток завладел мьютексом МТХВ и собирается завладеть мьютексом МТХА. Таким образом, каждый из потоков завладел только частью ресурсов, необходимых для продолжения его работы, и ожидает, пока вторая часть ресурсов освободится. Очевидно, что каждый из потоков не освободит первую часть ресурсов, пока не получит доступ ко второй части, однако это никогда не произойдет. В результате оба потока взаимно блокируют друг друга и не могут продолжить работу.

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

Рекомендации по поводу предотвращения взаимного блокирования потоков можно обнаружить фактически в любой хорошей книге, посвященной программированию в многозадачной среде. Самым лучшим оружием против взаимного блокирования является здравый смысл разработчика программы. Постарайтесь избегать ситуаций, в которых ваша программа ожидает освобождения ресурса ничем не ограниченное время. Ограничьте время ожидания некоторым конечным значением. Если в течение указанного вами периода времени ресурс все еще остается занятым, освободите все ресурсы, которыми владеет ваша программа, и спустя некоторое время повторите попытку завладеть полным комплектом ресурсов, необходимых для продолжения работы. В случае, аналогичном рассмотренному ранее, где речь идет о двух мьютексах МТХА и МТХВ, вы можете попытаться организовать работу обоих потоков таким образом, чтобы оба они пытались завладеть сначала мьютексом МТХА, а затем мьютексом МТХВ. В этом случае взаимного блокирования также удастся избежать.

Использование событий

Если событие будет использоваться потоками одного процесса, вы можете не присваивать этому событию имя. Чтобы создать событие, обратитесь к вызову CreateEvent и передайте ему четыре аргумента. Первый аргумент — атрибуты безопасности. Если вы намерены использовать атрибуты по умолчанию, присвойте этому аргументу значение NULL. Второй аргумент определяет тип события (мануалъное — manual или единичное — single), а третий аргумент соответствует начальному состоянию события. Наконец, последний аргумент соответствует имени события. Если вы хотите создать безымянное событие, передайте в качестве этого аргумента значение NULL.

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

Чтобы определить состояние события, вы можете обратиться к любой из функций слежения за состоянием объектов синхронизации (например, WaitForSingleObject). Более подробно об этом рассказано несколько ранее, в разделе «Выбор метода синхронизации».