Отладил на стареньком C++Builder. Голый API, должно работать всюду. С благодарностью рассмотрю любые предложения по части усовершенствования.
================================== .h =======================================
/* Был глюк при написании Shell Extension DLL (IconHandler): когда Explorer
загружал DLL, RTL не инициализировался, конструкторы статических объектов
не вызывались, DllEntryPoint не вызывалась. Класс AssStaticCriticalSection
обходит указанный глюк. Конструкторов/деструкторов нет. Инициализируется при
первом обращении к любому методу. Удаляется при уничтожении процесса. :))
Работает под 9x/NT.
Использование: для создания экземпляра класса пользуйтесь приведенными ниже
макросами, которые инициализируют данные объекта на этапе компиляции.
Пустой конструктор по умолчанию специально сделан приватным, чтобы нельзя
было по ошибке объявить объект обычным способом.
Реализация: используется mutex с именем "{GUID}/ProcessId/ObjectAddress".
Это позволяет на этапе инициализации хранить несколько хэндлов на один
mutex в автоматических переменных метода Initialize() в разных потоках
без конфликтов. Со вложенными вызовами Enter()/Leave() все нормально:
mutex'ы реализованы так же как и критические секции.
Замечания: возможно, стоит в проверках Leave() without Enter() выбрасывать
исключение вместо вызова assert(), который под BCB все рушит. */class AssStaticCriticalSection
{
private:
/* См. комментарии выше. */
AssStaticCriticalSection() {}
public:
/* Для внутреннего использования. Не получилось сделать private, поскольку
используется макросами. Два метода Initialize() и Perform() нужны для
скорости кода и во избежание конфликтов: Initialize() всегда открывает
mutex по имени и захватывает его, прежде чем обращаться к данным
объекта; Perform() вызывается в условиях, когда данные объекта уже
проинициализированы, то есть имеется готовый mutex handle, и не надо
тратить время на формирование имени mutex'а и его создание. */enum OPERATION { opTryEnter, opEnter, opLeave };
struct DATA
{
HANDLE hMutex;
bool (*pFunc)(DATA&, OPERATION);
};
DATA data;
static bool Initialize(DATA& data, OPERATION operation);
static bool Perform(DATA& data, OPERATION operation);
public:
/* Собственно интерфейс. */bool TryEnter() { return data.pFunc(data, opTryEnter); }
void Enter() { data.pFunc(data, opEnter); }
void Leave() { data.pFunc(data, opLeave); }
};
/* Макросы для объявления/определения объекта. Опираются на тот факт, что
sizeof(AssStaticCriticalSection::DATA) == sizeof(AssStaticCriticalSection). */#if sizeof(AssStaticCriticalSection::DATA) != sizeof(AssStaticCriticalSection)
#error sizeof(class AssStaticCriticalSection) is wrong!
#endif
#define DECLARE_PUBLIC_AssStaticCriticalSection(name) \
extern AssStaticCriticalSection& name;
#define DEFINE_PUBLIC_AssStaticCriticalSection(name) \
static AssStaticCriticalSection::DATA name##__data__ = {0, AssStaticCriticalSection::Initialize}; \
AssStaticCriticalSection& name = *(AssStaticCriticalSection*) & name##__data__;
#define DEFINE_STATIC_AssStaticCriticalSection(name) \
static AssStaticCriticalSection::DATA name##__data__ = {0, AssStaticCriticalSection::Initialize}; \
static AssStaticCriticalSection& name = *(AssStaticCriticalSection*) & name##__data__;
================================== .cpp =====================================
bool AssStaticCriticalSection::Initialize(DATA& data, OPERATION operation)
{
static const char* guid = "{C47770E8-85DB-45EC-9154-9CA77D33A910}";
//static const GUID guid = { 0xc47770e8, 0x85db, 0x45ec, { 0x91, 0x54, 0x9c, 0xa7, 0x7d, 0x33, 0xa9, 0x10 } };
/* Leave() without Enter(). Любой успешный вызов Enter() через эту
функцию ведет к изменению data.pFunc на Perform(), так что Leave()
никогда не должна здесь вызываться. */
assert(operation != opLeave); // Leave() without Enter()char name[60]; // 38+'/'+8+'/'+8+'\0'
sprintf(name, "%s/%08X/%08X", guid, GetCurrentProcessId(), (DWORD)&data);
HANDLE h = CreateMutex(NULL, false, name); // проверял: разным потокам возвращает разные хэндлы.
assert(h);
DWORD r = WaitForSingleObject(h, operation == opEnter ? INFINITE : 0);
assert(r != WAIT_FAILED);
/* Если TryEnter() и не смогли захватить mutex чтобы проинициализировать
data, ну и черт с ним, в следующий раз проинициализируем. Проверки на
Leave() without Enter() будут выполняться корректно. */if (r == WAIT_TIMEOUT)
{
CloseHandle(h);
return false;
}
assert(!data.hMutex);
data.hMutex = h;
data.pFunc = AssStaticCriticalSection::Perform;
return true;
}
//---------------------------------------------------------------------------bool AssStaticCriticalSection::Perform(DATA& data, OPERATION operation)
{
if (operation == opLeave)
{
assert(ReleaseMutex(data.hMutex)); // Leave() without Enter()return true;
}
else
{
DWORD r = WaitForSingleObject(data.hMutex, operation == opEnter ? INFINITE : 0);
assert(r != WAIT_FAILED);
return (r == WAIT_OBJECT_0 || r == WAIT_ABANDONED);
}
}
================================== <EOF> ====================================
Здравствуйте, Блудов Павел, Вы писали:
БП>Ой зря Вы это все затеяли! ИМХО, реализовывать критические секции через mutex'ы это нерентабельно...
Рентабельность мне не нужна. Структуру RTL_CRITICAL_SECTION я знаю, но не стал ею пользоваться (а) потому что она закрытая, а мне не хочется осложнять себе жизнь написанием различных версий кода для 9x и NT; (б) потому что это все равно никак не помогло бы мне сделать то, ради чего я весь сыр-бор и затеял: мне нужна была критическая секция, работающая без инициализатора (причину я описал в самом начале комментариев). Никаких конструкторов, вызовов InitializeCriticalSection, непосредственная готовность к работе, и, заметьте, корректная работа даже в случае, когда первый же вызов Lock() будет выполнен несколькими потоками одновременно. Вы можете реализовать такое же поведение, работая с RTL_CRITICAL_SECTION?
Здравствуйте, AssAsin, Вы писали:
AA> Вы можете реализовать такое же поведение, работая с RTL_CRITICAL_SECTION?
В Вашем конкретном примере это вполне возможно. Примерно так:
struct MyCS
{
CRITICAL_SECTION m_cs;
HANDLE _TearoffSemaphore();
void Enter();
bool TryEnter();
void Leave();
};
void MyCS::Enter()
{
if (::InterlockedIncrement(&m_cs.LockCount))
{
if (m_cs.OwningThread == (HANDLE)::GetCurrentThreadId())
{
m_cs.RecursionCount++;
return;
}
::WaitForSingleObject(_TearoffSemaphore(), INFINITE);
}
m_cs.OwningThread = (HANDLE)::GetCurrentThreadId();
m_cs.RecursionCount = 1;
}
bool MyCS::TryEnter()
{
if (-1L == ::InterlockedCompareExchange(&m_cs.LockCount, 0, -1))
{
m_cs.OwningThread = (HANDLE)::GetCurrentThreadId();
m_cs.RecursionCount = 1;
}
else if (m_cs.OwningThread == (HANDLE)::GetCurrentThreadId())
{
::InterlockedIncrement(&m_cs.LockCount);
m_cs.RecursionCount++;
}
else
return FALSE;
return TRUE;
}
void MyCS::Leave()
{
if (--m_cs.RecursionCount)
::InterlockedDecrement(&m_cs.LockCount);
else if (::InterlockedDecrement(&m_cs.LockCount) >= 0)
::ReleaseSemaphore(_TearoffSemaphore(), 1, NULL);
}
HANDLE MyCS::_TearoffSemaphore()
{
HANDLE ret = m_cs.LockSemaphore;
if (!ret)
{
HANDLE sem = ::CreateSemaphore(NULL, 0, 1, NULL);
ret = (HANDLE)::InterlockedCompareExchangePointer(&m_cs.LockSemaphore, sem, NULL);
if (!ret)
ret = sem;
else
::CloseHandle(sem);
}
return ret;
}
#define DEFINE_STATIC_MyCriticalSection(name) \
static MyCS name = { {NULL, -1, 0, 0, NULL, 0}};
DEFINE_STATIC_MyCriticalSection(testCS);
т.е. основная идея в том, что
1. Initialize делает за нас компилятор (впрочем, это как раз Ваша мысль)
2. Используются самодельные Enter/TryEnter/Leave — гарантия того, что код
будет работать и на 9.x/Me
Здравствуйте, Блудов Павел, Вы писали:
БП>bool MyCS::TryEnter() БП>{ БП> if (-1L == ::InterlockedCompareExchange(&m_cs.LockCount, 0, -1)) БП> ... БП>}
Если бы все было так просто, я бы и не извращался с mutex'ами. Однако в хелпах написано, что сия функция (и некоторые другие InterlockedXXX) есть только на NT. Вызывать ее под 98 я даже не пытался.
Черт, поторопился Никто ничего не заметил, правда?
БП>>bool MyCS::TryEnter() БП>>{ БП>> if (-1L == ::InterlockedCompareExchange(&m_cs.LockCount, 0, -1)) БП>> ... БП>>}
AA>Если бы все было так просто, я бы и не извращался с mutex'ами. Однако в хелпах написано, что сия функция (и некоторые другие InterlockedXXX) есть только на NT. Вызывать ее под 98 я даже не пытался.
Она есть на Windows 98, о чем недвусмысленно сказано в MSDN:
Windows 95/98/Me: Included in Windows 98 and later.
На Windows 95 ее действительно нет, но заменить на lock cmpxchg ничего не стоит.
Здравствуйте, Alex Fedotov, Вы писали:
AF>Windows 95/98/Me: Included in Windows 98 and later. AF>На Windows 95 ее действительно нет, но заменить на lock cmpxchg ничего не стоит.
Сдаюсь! Про существование ассемблера забыл полностью. Вот что значит отсутствие практики.
Здравствуйте, AssAsin, Вы писали:
AA>Сдаюсь! Про существование ассемблера забыл полностью. Вот что значит отсутствие практики.
Щас я Вас добью. Майкрософт официально отрекласть от Windows95 более года назад.
И уже сегодня компьютеры с win95 практически вымерли. Для стравки: на rsdn.ru
(сайт посвященный, в основном, программированию по Windows) в полтора раза
больше заходов с Linux'а, чем c Win95. Те 2,5 пользователя, которым хватает
Win95, с вероятностью 100% проживут и без Вашей программы.
Таким образом, имеем: Ваш код работает и на 95 и на XP. Мой — только на 98> .
Но, Ваш код работает _одинаково_медленно_ и под 95 и под XP. Получается,
что Вы кладете на 99.9% пользователей своей программы ради 0.1%? А как же
непоколебимые устои димократии? Ну ладно бы их был процент, ну пол-процента,
так их же по пальцам перечесть!
Следите за рынком! До сих пор есть программы, которые работают даже и под
3.1, и что из того? Кому они нужны? Да никому уже...
Здравствуйте, AssAsin, Вы писали:
AA> HANDLE h = CreateMutex(NULL, false, name); // проверял: разным потокам возвращает разные хэндлы. AA> assert(h); AA> DWORD r = WaitForSingleObject(h, operation == opEnter ? INFINITE : 0); AA> assert(r != WAIT_FAILED);
AA> /* Если TryEnter() и не смогли захватить mutex чтобы проинициализировать AA> data, ну и черт с ним, в следующий раз проинициализируем. Проверки на AA> Leave() without Enter() будут выполняться корректно. */
AA> if (r == WAIT_TIMEOUT) AA> { AA> CloseHandle(h); AA> return false; AA> }
AA> assert(!data.hMutex); AA> data.hMutex = h; AA> data.pFunc = AssStaticCriticalSection::Perform; AA> return true;
На лицо leak в функции AssStaticCriticalSection::Initialize(), если делается одновременно Enter() и TryLock(), они заходят в AssStaticCriticalSection::Initialize(), причем thead что дергал Enter() успевает выйти из Initialize() и сделать Leave(), thead выполняющий TryLock() создает новый handle на существующий mutex, спокойно проходит WaitForSingleObject() и переписывает handle созданный ранее (перед этим, правда, показав assert). В результате имеем один потерянный handle, малость, но неприятно.
Да leak будет и в том случаее, если TryLock() первым пройдет через WaitForSingleObject(). Так даже наглядней.