Windows C++ 中与 Java 的 LockSupport.parkNanos() 相等的函数。

3

我需要在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,我正在将LMAX的Disruptor模式移植到C ++。当一个线程在SingleThreadedClaimStrategy :: WaitForFreeSlotAt()中,另一个线程在BlockingWaitStrategy :: WaitFor(没有超时)中时,会发生死锁。当RingBuffer大小很小时(例如1、2、4、8等),死锁更加明显。

这些线程是通过正常的CreateThread方法创建的。

编辑:我写这篇文章时已经很晚了,所以这里有更多的信息。RingBuffer保存__int64。我有一个生产者线程和一个消费者线程。消费者线程还生成一个计时器线程,每秒钟轮询一次消费者以获取其最后消费的事件的序列号。当消费者没有任何进展而生产者也没有完成时,就会出现问题。生产者只是在循环中运行几亿次发布计数器。因此,我的输出看起来像这样:

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

这个问题只在Release模式下出现,在所有内容都被优化为速度的情况下才能真正复现。


你说你在使用 ::SwitchToThread() 时出现了死锁。它返回的是 true 还是 false? - Managu
@Managu 在正常执行期间,它可以同时返回两者。但是你的评论让我想到了在该函数返回false时对其进行自旋的想法。 - James
你是如何实现ReentrantLock和Condition的,例如BlockingWaitStrategy中使用的方式?你的锁实现是否可重入?条件变量是否原子地获取/释放相应的锁? - Managu
你是否在适当的位置插入了内存屏障?Java计算模型明确规定了对volatile原语的读写操作需要进行屏障,但C++并没有这样做。阅读更多关于Disruptor的内容,内存屏障看起来非常重要。 - Managu
@Managu 我已经尽可能将Java代码复制到其Windows等效版本中。感谢您的关注。我在这里向LMAX的人员提出了问题:https://groups.google.com/forum/?fromgroups#!topic/lmax-disruptor/-fyGkXrwyEU[1-25] - James
2个回答

3
除了能够使用unpark()唤醒线程外,LockSupport.parkNanos(...)仅仅是一个休眠。在Windows上的OpenJDK Hotspot VM中,它使用WaitForSingleObject(...)实现(第4436行),并且至少休眠1毫秒。
LMAX disruptor似乎从不unpark()线程。因此,通过调用Sleep(1)可以获得相同的行为。您可能可以通过Sleep(0)来做得更好:在当前线程中放弃剩余的时间片,并立即可用于重新调度。这相当于SwitchToThread(),但后者可能只会告诉您“尚未准备好运行,因此您可以保留CPU”。另一方面,如果您的调度粒度足够低,Sleep(1)实际上可能会暂停1毫秒。
注释 中提到,通过调用 timeBeginPeriod() 可以改善系统的调度粒度(可能降至每个时钟周期1毫秒)。请注意,此处保留了HTML标签。

2

parkNanos没有源代码可用;它是在原生的Sun库中实现的。

该原生库的源代码应该是OpenJDK 6/7源代码的一部分,因此可以下载或浏览。


这意味着在Windows上,parkNanos() 只等待毫秒级分辨率:http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/os/windows/vm/os_windows.cpp,第4451行。 - Managu
我搜索了很多次,但始终找不到。谢谢。 - James

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接