#c #visual-c #mfc #thread-synchronization
#c #visual-c #mfc #синхронизация потоков
Вопрос:
Я был задействован для поддержки некоторого устаревшего кода, и я вижу некоторые вещи, которые заставляют меня в замешательстве чесать голову. В некоторых разделах кода я вижу, что экземпляр класса использует экземпляр CMutex для синхронизации выполнения метода. Например
class CClassA : public CObject
{
public:
void DoSomething();
private:
CMutex m_mutex;
}
void CClassA::DoSomething()
{
m_mutex.Lock();
//...logic...
m_mutex.Unlock();
}
В другом месте того же проекта я обнаружил, что код использует CSingleLock
class CClassB : public CObject
{
public:
void DoSomething();
private:
CCriticalSection m_crit;
}
void CClassB::DoSomething()
{
CSingleLock lock(amp;m_crit);
lock.Lock();
//...logic...
lock.Unlock();
}
После просмотра документации MSDN по синхронизации может показаться, что CClassB реализует рекомендованный метод, но мне не ясно, в чем опасность реализации, используемой CClassA. Насколько я могу судить, единственное различие между двумя методами заключается в том, что CSingleLock имеет преимущество RAII, поэтому блокировка автоматически снимается, когда выполнение выходит из области видимости. Есть ли какие-либо другие преимущества / недостатки в любой реализации?
Комментарии:
1. Чтобы добавить некоторый контекст, одна из моих проблем заключается в том, имеет ли использование CSingleLock::Lock слегка отличающееся поведение. Например, когда один и тот же поток может вызывать CMutex::Lock несколько раз (поскольку он уже владеет блокировкой), вызов CSingleLock::Lock в том же экземпляре CSingleLock будет заблокирован. Я также обеспокоен тем, что могу столкнуться с ситуацией, когда CSingleLock управляет CCriticalSection, но у этого CCriticalSection есть метод разблокировки, вызываемый напрямую.
Ответ №1:
В общем случае мьютексы могут использоваться для управления доступом к потокам между процессами через именованный мьютекс, в то время как критические секции предназначены только для синхронизации доступа к потокам в одном и том же пространстве процесса.
Ни один из этих классов по-настоящему не получает преимущества RAII без их переноса, потому что в этом случае вам никогда не понадобилось бы явно вызывать lock или unlock. Возьмем, к примеру, этот небольшой фрагмент псевдокода, использующий блокировку мьютекса boost …
void DoSomething()
{
// construction acquires lock on mutex
boost::scoped_lock lock(amp;aBoostMutex);
// ...
} // end scope - object is destroyed and lock is released
Теперь я бы сказал, что вам следует избегать CMutex
, CCritalSection
, CSemaphore
, и CEvent
потому что реализации несколько нарушены или, по крайней мере, уступают другим доступным библиотекам, таким как boost. Например:
- Определить время ожидания из заброшенного мьютекса невозможно, потому что реализация проверяет только возвращаемое значение, а не причину.
- Использование реентерабельных блокировок не используется,
CSingleLock
поэтому рекурсия вызовет проблемы. - Невозможность получить именованное событие между процессами
В зависимости от поставленной перед вами задачи у вас может быть возможность отказаться от оболочек MFC в Windows API и либо реализовать свои собственные атомарные блокировки, либо использовать что-то вроде функций boost или C 0x, подобных std::mutex
, которые не только являются лучшими реализациями, но и обеспечивают кроссплатформенную поддержку.
Комментарии:
1. К сожалению, я застрял с MFC. Мой вопрос был не столько о разнице между CMutex и CCriticalSection, сколько о разнице между CSyncObject::Lock и CSingleLock::Lock. После проведения дополнительных исследований выяснилось, что CSingleLock ведет себя аналогично boost::scoped_lock, который вы показали выше (вызывает разблокировку при деструкторе). Я не понимал, что CSingleLock предотвращает рекурсию, я предполагал, что каждый новый экземпляр в стеке будет управляться независимо.
2.
CSyncObject
вызывает Windows apiWaitForSingleObject
под прикрытием, насколько я знаю, и не может использоваться напрямую. Это интерфейс базового класса, которыйCMutex
является производным от, поэтому он используется, когда вы выполняетеCMutex::Lock();
в своем экземпляре.CSingleLock
является ли вашCCriticalSection
манипулятор конкретным и может быть использован в качестве блокировки области видимости с некоторым переносом. Как упоминалось ранее, с этими объектами существуют некоторые проблемы с дизайном, поэтому их следует избегать flounder.com/avoid_mfc_syncrhonization.htm3. Для будущих читателей, CSingleLock имеет параметр конструктора , который приведет к блокировке при построении. В примере OP он не используется, что как бы опровергает основную причину использования CSingleLock.
Ответ №2:
Критический раздел виден только потокам внутри одного процесса и может быть использован ими. Мьютекс можно сделать видимым для ряда процессов (обычно путем создания именованного мьютекса). Того, что вы показали выше, недостаточно, чтобы сказать, является ли это причиной того, что у них есть оба, но это одна из возможностей.
Комментарии:
1. Из того, что я могу сказать, этот код никогда не нуждается в синхронизации, которая пересекает границы процесса. Это наводит меня на мысль, что CMutex в CCLassA может быть заменен CCriticalSection.