xenotron
Просто чтобы прояснить ситуацию, в учебниках по потоковой передаче термины "мьютекс", "критическая секция" и "блокировка" являются взаимозаменяемыми, они означают одно и то же. В каждой операционной системе их несколько видов. Виды, которые я обычно использую, следующие:
- блокировка вращения
- нормальная блокировка между потоками одного процесса
- межпроцессная блокировка для синхронизации между любыми потоками любых процессов, например, если Вы делитесь данными через общую память между несколькими процессами
Spin locks are evil, they are usually used only in case of hardcore optimizations and in case where the time a thread holds a lock is extremely small. You may never need it in your life but good to know about its existence. A normal lock is perfect to synchronize access to a shared resource between several threads inside your program. This is the kind of lock you will use in most cases, it is usually better in performance than an interprocess lock, so dont use interprocess locks when you don't need them. In windows the name of the normal lock is CRITICAL_SECTION, while the name of the inter-process lock is Mutex. Don't get confused by these names, they could have given the "Mutex" name to the normal lock if they wanted to do so, they have just used synonyms to differentiate them. So in most cases you will use the CRITICAL_SECTION in windows. Each platform has different thread synchronization primitives and in case of performance critical scenarios you have to find out or test out which one works best for your case.
Еще одним важным свойством блокировок является то, являются ли они рекурсивными или нет. Например, windows CRITICAL_SECTION по умолчанию рекурсивна. Это означает, что после того, как поток получает блокировку в первый раз, он может получить блокировку столько раз, сколько он хочет без блокировки, но он должен освободить блокировку столько же раз, сколько он был получен. Нерекурсивная блокировка проще и обычно немного быстрее, чем рекурсивная, но если ваш поток пытается получить ее во второй раз, не освобождая первый, он попадает в тупик.
Вот как я реализую обычную блокировку на windows:
class CLock
{
public:
CLock()
{
InitializeCriticalSection(&m_CriticalSection);
}
~CLock()
{
DeleteCriticalSection(&m_CriticalSection);
}
void Acquire()
{
EnterCriticalSection(&m_CriticalSection);
}
void Release()
{
LeaveCriticalSection(&m_CriticalSection);
}
private:
CLock(const CLock&); // disabling copy constructor
CRITICAL_SECTION m_CriticalSection;
};
// Using RAII to make unlocking cleaner and safer
class CAutoLock
{
public:
CAutoLock(CLock& lock)
: m_Lock(lock)
{
m_Lock.Acquire();
}
~CAutoLock()
{
m_Lock.Release();
}
private:
CLock &m_Lock;
};
#define CONCAT_MACRO1(var_name, counter) var_name##counter
#define CONCAT_MACRO2(var_name, counter) CONCAT_MACRO1(var_name, counter)
#define CONCAT_COUNTER(var_name) CONCAT_MACRO2(var_name, __COUNTER__)
// automatic name generation for the __auto_lock stack object
#define LOCK_SCOPE(var) CAutoLock CONCAT_COUNTER(__auto_lock)(var)
CLock m_Lock2;
class CTest
{
public:
void DoJob()
{
{
LOCK_SCOPE(m_Lock1);
LOCK_SCOPE(m_Lock2);
// Do job with resources protected by m_Lock1 and m_Lock2
} // both m_Lock2 and m_Lock1 are released at the end of the scope or if you
// return from the function in reverse order of how you acquired them
LOCK_SCOPE(m_Lock1);
bool success = DoJob2();
if (!success)
return; // m_Lock1 is released automatically
// do something
} // m_Lock1 is released automatically
private:
bool DoJob2()
{
// anything you want
return true;
}
private:
CLock m_Lock1;
};
В небольших программах можно использовать предварительно испеченные классы блокировки из mfc, boost или стандартной библиотеки. Однако если вы делаете что-то серьезное, то ваша собственная реализация может вам очень помочь. Вы можете поместить отладочные материалы в свои часы, CMutex или любые другие классы. Например, в нерекурсивном классе блокировки вы можете утверждать, когда один и тот же поток пытается получить блокировку во второй раз, что облегчает раннее обнаружение больших ошибок/тупиков. В классе spinlock вы можете подсчитать количество спинов, чтобы узнать, является ли spinlock где-то эффективным или нет...
Sergey Alexandrovich Kryukov
Хорошая работа, 5 баллов.
Хорошо, что вы упомянули критический раздел. Мьютекс Windows, в большинстве случаев, был бы излишним. Критическая секция имеет малый вес, поэтому она лучше подходит для in-proc вещей (где мьютекс может быть безымянным).
—СА
xenotron
Спасибо! Это может быть предметом "продвинутого" вопроса о потоковой передаче windows, многие люди, которые учатся из учебника, ссылаются только на мьютекс (поскольку его название используется в большинстве учебников).