Почему не работает сон?

Почему в следующем коде c_sleep немедленно возвращается?

{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign.C.Types
import Data.Time.Clock
import Control.Concurrent

foreign import ccall unsafe "unistd.h sleep" 
    c_sleep :: CUInt -> IO CUInt

main :: IO ()
main = do
    getCurrentTime >>= print . utctDayTime
    c_sleep 10     >>= print                -- this doesn't sleep
    getCurrentTime >>= print . utctDayTime
    threadDelay $ 10 * 1000 * 1000          -- this does sleep
    getCurrentTime >>= print . utctDayTime
$ ghc --make Sleep.hs && ./Sleep
[1 of 1] Compiling Main             ( Sleep.hs, Sleep.o )
Linking Sleep ...
29448.191603s
10
29448.20158s
29458.211402s

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.8.3

$ cabal --version
cabal-install version 1.20.0.3
using version 1.20.0.0 of the Cabal library 

Примечание. На самом деле, я хотел бы использовать sleep в коде C, чтобы смоделировать некоторые тяжелые вычисления в функции func и вызвать эту функцию в Haskell, но это тоже не работает, вероятно, по тем же причинам.


person Zeta    schedule 03.02.2015    source источник
comment
Возможно, среда выполнения GHC использует сигналы, нарушающие sleep. Вы проверили код ошибки? Возможно, вам следует заключить его в цикл, чтобы перезапустить его, если он будет прерван (в этом случае лучше использовать nanosleep для более высокой точности).   -  person Rufflewind    schedule 03.02.2015
comment
@Rufflewind: sleep не возвращает код ошибки, но количество невыспавших в секундах: /. Еще не пробовал nanosleep, но usleep тоже не работает.   -  person Zeta    schedule 03.02.2015
comment
Код ошибки возвращается через переменную errno. Оберните (c_sleep 10) внутрь throwErrnoIf (/= 0) "sleep", и вы увидите, что это прерывается.   -  person Rufflewind    schedule 03.02.2015
comment
См. Также эту ошибку. Не совсем связано с этой проблемой, но в нем говорится, что RTS, даже однопоточная, периодически отправляет сигналы, которые прерывают любой вызов sleep.   -  person Rufflewind    schedule 03.02.2015
comment
@Rufflewind: Спасибо. Боже, почему man 3 sleep не упоминает об обновлении errno? Думаю, мне тоже придется написать void nanosleep_loop(uint32_t), так как nanosleep тоже пострадает. Билет, вероятно, отвечает на этот вопрос, поэтому не стесняйтесь добавлять его.   -  person Zeta    schedule 03.02.2015
comment
На самом деле, вы правы, в документации для sleep вообще ничего не упоминается о errno (вместо этого я читал nanosleep). Однако он говорит, что если возвращаемое значение ненулевое, то это произошло из-за прерывания сигналом.   -  person Rufflewind    schedule 03.02.2015


Ответы (2)


RTS, похоже, использует сигналы для своего собственный tasks, что означает, что скоро сон будет прерван одним из этих сигналов. Я тоже не думаю, что это ошибка, среда выполнения имеет свою собственную территорию, так сказать. Хаскеллианский подход заключался бы в использовании threadDelay, но программе на C нелегко получить к нему доступ без некоторых уловок.

правильный способ - это неоднократно возобновлять сон, несмотря на прерывания. от других сигналов. Я рекомендую использовать nanosleep, так как sleep имеет точность только в секунды, а сигналы появляются гораздо чаще, чем это.

#include <errno.h>
#include <time.h>

/* same as 'sleep' except it doesn't get interrupted by signals */
int keep_sleeping(unsigned long sec) {
    struct timespec rem, req = { (time_t) sec, 0 }; /* warning: may overflow */
    while ((rem.tv_sec || rem.tv_nsec) && nanosleep(&req, &rem)) {
        if (errno != EINTR) /* this check is probably unnecessary */
            return -1;
        req = rem;
    }
    return 0;
}
person Rufflewind    schedule 03.02.2015

Все примитивы параллелизма всегда имеют оператор clawback, который они могут блокировать на меньшее время, чем указано - они могут ложно возвращаться. Это не имеет отношения к языку, это природа параллелизма, поэтому, если вы хотите подождать точно указанное количество времени, на любом языке вам нужно построить цикл, проверяющий часы после сна.

person Sassa NF    schedule 03.02.2015
comment
Хотя это так, нельзя ожидать, что действительный вызов (u)sleep(), который обычно хорошо работает в однопоточном домене C, будет прерван сразу после вызова. Тем более, если не использовались -threaded и / или параллелизм. - person Zeta; 03.02.2015
comment
@Zeta Значит, ожидание неверно. Никто никогда не указывает причину ложного пробуждения - это ложное; он не говорит "may wake up *only* because the application interrupted itself". Я не думаю, что имеет смысл интуитивно кодировать неопределенное поведение - например, мы не предполагаем, что неинициализированный int равен нулю в C. - person Sassa NF; 03.02.2015