Windows C++ эквивалент Java LockSupport.parkNanos()

Мне нужно реализовать ту же функциональность, что и эта функция на Win7 x64.

Сначала я использовал SwitchToThread(), но это не работает, так как вызывает взаимоблокировку в экстремальных условиях. Единственная альтернатива, которую я могу найти, это Sleep(), но это, вероятно, будет убийцей производительности, поскольку он работает только с миллисекундным разрешением, и я все еще не уверен, что он делает то же самое, что и LockSupport.parkNanos().

Я нашел способность Java планировать (если это то, что происходит) потоки с наносекундным интервалом подозрительной, поэтому я реализовал то, что, как я мог только предположить, они делают... вращаются. Однако я не уверен, что это решает проблему, возможно, это просто отсрочка неизбежного, поскольку кажется, что функция Java требует вмешательства JVM для работы. Исходный код для parkNanos недоступен; он реализован в родной библиотеке Sun.

class LockSupport
{
public:
    static void ParkNanos(unsigned __int64 aNanos)
    {
        ULONGLONG start;
        ULONGLONG end;

        ::QueryUnbiasedInterruptTime(&start);
        do
        {
            // My issue with this is that nothing is actually 'Parked'.
            ::SwitchToThread();
            ::QueryUnbiasedInterruptTime(&end);
        }
        while ((end - start) < aNanos);
    }
};

Код вызова выглядит так:

void SomeClass::SomeFunction()
{
    while (someCond)
    {
        LockSupport.parkNanos(1L);
    }
}

FWIW, я переношу шаблон Disruptor от LMAX на C++. Взаимоблокировка происходит, когда один поток находится в SingleThreadedClaimStrategy::WaitForFreeSlotAt(), а другой в BlockingWaitStrategy::WaitFor (без тайм-аута). Тупик более очевиден, когда размер RingBuffer крошечный... 1, 2, 4, 8 и т. д.

Потоки создаются обычными CreateThread средствами.

Редактировать: было довольно поздно, когда я написал это, так что вот еще немного информации. RingBuffer содержит __int64s. У меня есть один поток Producer и один поток Consumer. Поток Consumer также порождает поток Timer, который каждую секунду опрашивает Consumer на предмет порядкового номера события, которое он использовал в последний раз. Наступает момент, когда Потребитель не продвигается вперед, а Производитель не закончил. Производитель просто запускается в цикле несколько сотен миллионов раз, публикуя счетчик. Итак, мой вывод выглядит примерно так:

898
97
131
Timer: no progress
Timer: no progress
...

Это действительно воспроизводимо только в режиме выпуска, где все оптимизировано для скорости.


person James    schedule 16.08.2012    source источник
comment
Вы говорите, что зашли в тупик с ::SwitchToThread(). Он возвращает true или false?   -  person Managu    schedule 16.08.2012
comment
@Managu Во время обычного выполнения он может вернуть оба. Однако ваш комментарий натолкнул меня на мысль использовать эту функцию, пока она возвращает false.   -  person James    schedule 16.08.2012
comment
Как вы реализуете ReentrantLock и Condition, то есть как они используются BlockingWaitStrategy? Является ли ваша реализация блокировки реентерабельной? Переменные условия атомарно получают/освобождают соответствующую блокировку?   -  person Managu    schedule 17.08.2012
comment
Вы установили барьеры памяти в соответствующих местах? Модель вычислений Java явно устанавливает барьеры для чтения и записи volatile примитивов, но C++ не делает этого. Читая больше о Disruptor, барьеры памяти выглядят важными.   -  person Managu    schedule 17.08.2012
comment
@Managu Я скопировал как можно больше кода Java в его эквивалент для Windows. Спасибо за ваш интерес. Я задал вопрос людям LMAX здесь: groups.google.com/forum/?fromgroups#!topic/lmax-disruptor/   -  person James    schedule 17.08.2012


Ответы (2)


Помимо возможности unpark() потока, LockSupport.parkNanos(...) не что иное, как сон. В виртуальной машине точки доступа OpenJDK в Windows это реализован (строка 4436) с использованием WaitForSingleObject(...) и приостанавливается минимум на 1 мс.

Разрушитель LMAX никогда не использует unpark() потока. Следовательно, вы должны получить эквивалентное поведение, вызвав Sleep(1). Возможно, вы могли бы добиться большего успеха с Sleep(0): вы отказываетесь от оставшейся части своего временного интервала в текущем потоке и сразу же становитесь доступным для повторного планирования. Это эквивалентно SwitchToThread(), за исключением того, что последний может просто сказать вам, что «ничего еще не готово к запуску, так что вы можете оставить процессор». С другой стороны, Sleep(1) может фактически приостановиться на 1 мс, если ваша гранулярность планирования достаточно низкая.

В примечаниях к Sleep() отмечено, что вы можете улучшить детализацию планирования вашей системы (возможно, до 1 мс на такт), вызвав timeBeginPeriod().

person Managu    schedule 16.08.2012

Исходный код для parkNanos недоступен; он реализован в родной библиотеке Sun.

Исходный код для этой собственной библиотеки должен быть частью исходного кода OpenJDK 6/7 и, следовательно, должен быть доступен для загрузки или просмотра.

person Stephen C    schedule 16.08.2012
comment
Это говорит о том, что parkNanos() в Windows ожидает разрешения только в миллисекундах: " rel="nofollow noreferrer">hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/os/, строка 4451 - person Managu; 16.08.2012
comment
Я искал и искал, но так и не смог найти это. Спасибо - person James; 16.08.2012