0

Я использую критический раздел Embarcadero, TCriticalSection,, но их документация не отвечает на мой вопрос.Если поток вызывает Acquire() в критическом разделе, будет ли эта блокировка освобождена, если другой поток вызывает Release()?

Если мы имеем глобальную критическую секцию объекта:

namespace 
{ 
    //delphi style class must be constructed on the heap 
    TCriticalSection* criticalSection = new TCriticalSection(); 
} 

//somewhere in thread 1... 
criticalSection->Acquire(); 

//somewhere in thread2... 
criticalSection->Release(); 

Будет ли вызов релиз в thread2 раскрыться критическую секцию?

Я спрашиваю, потому что у меня есть класс RAII, который освобождает блокировку во время уничтожения, поэтому мы не вступаем в тупик, если код вызывает исключение. Однако критический раздел является лишь частью метода.

//... 
CRITICAL_SECTION_LOCK lock(criticalSection); 
OneAtATimePlease(); 
lock.Release(); 
//... 

Так что я не хочу, чтобы поместить в критическую секцию внутри OneAtATimePlease(), потому что тогда мы будем обработка замков для каждого вызова этого метода в коде.

ответ

1

TCriticalSection является оболочкой для Win32 Critical Section object, где Acquire() просто вызывает EnterCriticalSection() и Release() просто вызывает LeaveCriticalSection().

В LeaveCriticalSection() документации говорится:

Нить использует функцию EnterCriticalSection или TryEnterCriticalSection приобрести в собственность критического объекта раздела. Чтобы освободить свою собственность, поток должен вызывать LeaveCriticalSection один раз за каждый раз, когда он вступил в критический раздел.

Если поток вызывает LeaveCriticalSection, когда он не имеет права собственности на указанный объект критической секции возникает ошибка, которая может привести к другой поток с помощью EnterCriticalSection ждать до бесконечности.

Итак, НЕ пытайтесь разблокировать критический раздел в потоке, который в настоящее время не владеет блокировкой.

Это совершенно нормально (и предпочтительный) для вас, чтобы переместить lock переменную внутри OneAtATimePlease(), где она принадлежит:

void OneAtATimePlease() 
{ 
    CRITICAL_SECTION_LOCK lock(criticalSection); 
    ... 
} 

Подумайте о том, что будет происходить с помощью исходного кода, если несколько потоков называют OneAtATimePlease() на то же самое время, но поток не блокирует критическую секцию:

тему 1

CRITICAL_SECTION_LOCK lock(criticalSection); 
OneAtATimePlease(); 
lock.Release(); 

Тема 2

CRITICAL_SECTION_LOCK lock(criticalSection); 
OneAtATimePlease(); 
lock.Release(); 

Тема 3

// NO LOCK!!! 
OneAtATimePlease(); 

Тема 3 может выполнить OneAtATimePlease()в то время как Пропустите 1 или 2 уже внутри него! Это побеждает всю цель использования критического раздела. Если вы переместите блокировку внутри OneAtATimePlease(), то нет возможности для того, чтобы несколько потоков не синхронизировались друг с другом (если только один из них не ошибочно не разблокирует критический раздел, когда он не владеет блокировкой, но ваша оболочка RAII предотвратит это) ,

Это даже работать рекурсивно и безопасно, согласно documentation:

Когда поток имеет критическую секцию, он может сделать дополнительные вызовы EnterCriticalSection или TryEnterCriticalSection, не блокируя его исполнение. Это предотвращает зависание потока в ожидании критической секции, которой он уже владеет. Чтобы освободить свою собственность, поток должен вызывать LeaveCriticalSection один раз за каждый раз, когда он вступил в критический раздел.

void OneAtATimePlease() 
{ 
    CRITICAL_SECTION_LOCK lock(criticalSection); 
    ... 
    if (some condition) 
     OneAtATimePlease(); 
    .... 
} 
+0

Спасибо за четкий и подробный ответ! –

2

«Критическая секция» - это действительно концепция MS Windows. Не определено, что произойдет, если поток не сможет разблокировать критический раздел, а также не определено, что произойдет, если другой поток попытается разблокировать критический раздел, который он не заблокировал.

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

+0

Я добавил немного логики класса RAII для обработки события двойного релиза, но метод Release() из CRITICAL_SECTION_LOCK только заканчивает тем, что делает criticalSection-> Release(). Таким образом, хотя объект блокировки является локальным потоком, операция выполняется на глобальном TCriticalSection, поэтому освобождение от разных потоков будет неопределенным тогда? –

+0

@syco_link: Было бы разумно добавить 'assert' в режим отладки, чтобы вы могли поймать нарушающий код. И да, когда вы пытаетесь разблокировать критический раздел, вы можете освободить блокировку, удерживаемую текущим потоком, а не случайную другую блокировку на каком-то другом потоке. – MSalters