Критические секции и обработка ошибок
Вероятность того, что lnitializeCriticalSection потерпит неудачу, крайне мала, но все же существует. В свое время Microsoft не учла этого при разработке функции и определила ее возвращаемое значение как VOID, т. e. она ничего не возвращает. Однако функция может потерпеть неудачу, так как выделяет блок памяти для внутрисистемной отладочной информации. Если выделить память не удается, генерируется исключение STATUS_NO_MEMORY. Вы можете перехватить его, используя структурную об работку исключений (см. главы 23, 24 и 25).
Есть и другой, более простой способ решить эту проблему — перейти на новую функцию InitializeCriticalSectionAndSpinCount. Она, тоже выделяя блок памяти для отладочной информации, возвращает FALSE, если выделить память не удается.
В работе с критическими секциями может возникнуть еще одна проблема. Когда за доступ к критической секции конкурирует два и более потоков, она использует объект ядра "событие". (Я покажу, как работать с этим объектом при описании C++ класса COptex в главе 10.) Поскольку такая конкуренция маловероятна, система не создает объект ядра "событие" до тех пор, пока он действительно не потребуется. Это экономит массу системных ресурсов — в большинстве критических секций конкуренция потоков никогда не возникает.
Но если потоки все же будут конкурировать за критическую секцию в условиях нехватки памяти, система не сможет создать нужный объект ядра. И тогда EnterCriticalSection возбудит исключение EXCEPTION_INVALID_HANDLE. Большинство разработчиков просто игнорирует вероятность такой ошибки и не предусматривает для нее никакой обработки, поскольку она случается действительно очень редко. Но если Вы хотите заранее подготовиться к такой ситуации, у Вас есть две возможности.v
Первая — использовать структурную обработку исключений и перехватывать ошибку. При этом Вы либо отказываетесь от обращения к ресурсу, защищенному критической секцией, либо дожидаетесь появления свободной памяти, а затем повторяете вызов EnterCriticalSection.
Вторая возможность заключается в том, что Вы создаете критическую секцию вызовом InitializeCriticalSectionAndSpinCount, передавая параметр dwSpinGount с установленным старшим битом. Тогда функция создает объект "событие" и сопоставляет его с критической секцией. Если создать объект не удается, она возвращает FALSE, и это позволяет корректнее обрабатывать такие ситуации. Но успешно созданный объект ядра "событие" гарантирует Вам, что EnterCriticalSection выполнит свою задачу при любых обстоятельствах и никогда не вызовет исключение. (Всегда выделяя память под объекты ядра "событие", Вы неэкономно расходуете системные ресурсы. Поэтому делать так следует лишь в нескольких случаях, а именно: если программа может рухнуть из-за неудачного завершения функции EnterCriticatlSection, если Вы уверены в конкуренции потоков при обращении к критической секции или если программа будет работать в условиях нехватки памяти.)