초당 정확히 한 번씩 루프 실행


33

매초마다 몇 가지 사항을 확인하고 인쇄하기 위해이 루프를 실행하고 있습니다. 그러나 계산에 수백 밀리 초가 걸리기 때문에 인쇄 시간이 때때로 1 초를 건너 뜁니다.

매 초마다 인쇄물을 얻을 수 있도록 루프를 작성하는 방법이 있습니까? (물론 루프에서 계산하는 데 1 초도 걸리지 않습니다.)

while true; do
  TIME=$(date +%H:%M:%S)
  # some calculations which take a few hundred milliseconds
  FOO=...
  BAR=...
  printf '%s  %s  %s\n' $TIME $FOO $BAR
  sleep 1
done

아마도 도움이 될 것입니다 : unix.stackexchange.com/q/60767/117549
Jeff Schaller

26
"주의 정확하게 당신이 (보통)는 적합한을보고 같은 코드를 예약하는 선제 멀티 태스킹 커널 꼭대기에 사용자 공간에서 실행되기 때문에 대부분의 경우 그대로 수 없습니다 초에 한 번씩는"(그래서 당신은 수면 후 즉시 제어를 회복하지 않을 수도 예를 들어 종료). sched(7)API 를 호출하는 C 코드를 작성하지 않는 한 (POSIX : 참조 <sched.h>및 링크 된 페이지) 기본적으로이 형식을 실시간으로 보장 할 수는 없습니다.
Kevin

@Kevin이 말한 것을 백업하기 위해 sleep ()을 사용하여 모든 종류의 정확한 타이밍을 얻으려고 하면 실패로 끝날 것입니다. 당신이 경우 정말 정확한 타이밍이 필요하면, 당신은> 1 초 실행하려면 취함으로써 자신을 여행하지 않는 시간 이후 - 마지막 이벤트가 +1에 따라 (CLOCK_MONOTONIC 참조) 트리거 일 시스템 시계를보고해야하고, 확인 다음 작업 등을 수행 한 후 다음 시간 계산
John U

여기이 떠날 것 falsehoodsabouttime.com
Malbarez ALO

정확히 1 초에 한 번 = VCXO를 사용하십시오. 소프트웨어 전용 솔루션은 "충분한"성능 만 제공하지만 정확하지는 않습니다.
이안 맥도날드

답변:


65

원래 코드에 조금 더 가깝게 유지하려면 내가하는 일은 다음과 같습니다.

while true; do
  sleep 1 &
  ...your stuff here...
  wait # for sleep
done

이것은 의미를 약간 변경합니다. 만약 당신의 물건이 1 초도 걸리지 않았다면, 그것은 1 초가 지나기를 기다릴 것입니다. 그러나 어떤 이유로 든 물건이 1 초 이상 걸리는 경우 끝없는 결코 더 많은 하위 프로세스를 생성하지 않습니다.

따라서 백그라운드에서가 아니라 병렬로 작업하지 않으므로 변수도 예상대로 작동합니다.

추가 백그라운드 작업을 시작하는 경우 프로세스를 구체적으로 wait대기하기 만하도록 지시를 변경해야합니다 sleep.

더 정확해야하는 경우 시스템 시계와 동기화하고 전체 초 대신 ms를 절전 모드로 전환하면됩니다.


시스템 시계와 동기화하는 방법? 정말 어리석은 시도 :

태만:

while sleep 1
do
    date +%N
done

출력 : 003511461 010510925 016081282 021643477 028504349 03 ... (계속 증가)

동기화 됨 :

 while sleep 0.$((1999999999 - 1$(date +%N)))
 do
     date +%N
 done

출력 : 002648691 001098397 002514348 001293023 001679137 00 ... (유지)


9
이 수면 / 대기 트릭은 정말 영리합니다!
philfr

sleep핸들 소수 초의 모든 구현이 궁금 하십니까?
jcaron

1
@jcaron은 그들 모두가 아닙니다. 그러나 그것은 gnu sleep 및 busybox sleep에서 작동하므로 이국적이지 않습니다. sleep 0.9 || sleep 1유효하지 않은 매개 변수가 수면이 실패하는 유일한 이유 와 같이 간단한 폴백을 수행 할 수 있습니다.
frostschutz

@frostschutz 나는 순진한 구현 sleep 0.9으로 해석 될 것으로 기대 합니다 sleep 0(그것이 atoi할 것입니다). 실제로 오류가 발생하는지 확실하지 않습니다.
jcaron

1
이 질문이 많은 관심을 끌게되어 기쁩니다. 당신의 제안과 답변은 매우 좋습니다. 그것은 초 안에 유지 될뿐만 아니라 가능한 한 초 전체에 가깝게 붙어 있습니다. 인상적! (PS! 참고로, GNU Coreutils를 설치 gdate하고 macOS에서 사용해야 date +%N작동합니다.)
forthrin

30

루프를 스크립트 / oneliner로 재구성 할 수 있다면 가장 간단한 방법 watchprecise옵션입니다.

당신은 효과를 볼 수 있습니다 watch -n 1 sleep 0.5-그것은 초 카운트 업을 표시하지만 때로는 1 초를 건너 뜁니다. 이것을 watch -n 1 -p sleep 0.5초당 실행하면 초당 두 번 출력되며 건너 뛰기가 표시되지 않습니다.


11

백그라운드 작업으로 실행되는 서브 쉘에서 작업을 실행하면 작업을 방해하지 않습니다 sleep.

while true; do
  (
    TIME=$(date +%T)
    # some calculations which take a few hundred milliseconds
    FOO=...
    BAR=...
    printf '%s  %s  %s\n' "$TIME" "$FOO" "$BAR"
  ) &
  sleep 1
done

1 초에서 "도난당한"유일한 시간은 서브 쉘을 시작하는 데 걸리는 시간 일 것이므로 결국 1 초는 건너 뛰지 만 원래 코드보다 자주 사용되지는 않을 것입니다.

사용하여 서브 쉘 종료의 코드 경우 보다 두 번째 이상은, 루프는 축적 백그라운드 작업을 시작하고 결국 자원의 고갈 것이다.


9

다른 대안 (예를 watch -p들어 Maelstrom이 제안한대로 사용할 수없는 경우 )은 sleepenh[ 맨 페이지 ]이며,이를 위해 설계되었습니다.

예:

#!/bin/sh

t=$(sleepenh 0)
while true; do
        date +'sec=%s ns=%N'
        sleep 0.2
        t=$(sleepenh $t 1)
done

을 참고 sleep 0.2200ms의 주위에 먹는 시간이 걸리는 작업을하고 거기에 시뮬레이션을. 그럼에도 불구하고 나노초 출력은 안정적으로 유지됩니다 (비 실시간 OS 표준에 따라). 초당 1 회 발생합니다.

sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687

1ms 미만이며 추세가 없습니다. 꽤 좋습니다. 시스템에 부하가 있지만 시간이 지남에 따라 드리프트가 없으면 최소 10ms의 바운스를 기대해야합니다. 즉, 당신은 초를 잃지 않을 것입니다.


7

zsh:

n=0
typeset -F SECONDS=0
while true; do
  date '+%FT%T.%2N%z'
  ((++n > SECONDS)) && sleep $((n - SECONDS))
done

수면 포인트 초 부동 지원하지 않는 경우 사용할 수 zshzselect(A 후 대신 zmodload zsh/zselect) :

zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
  date '+%FZ%T.%2N%z'
  ((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done

루프의 명령이 실행되는 데 1 초도 걸리지 않는 한 드리프트되지 않아야합니다.


0

모든 도우미 (usleep, GNUsleep, sleepenh 등)를 사용할 수없는 POSIX 셸 스크립트에 대해 정확히 동일한 요구 사항이 있습니다.

참조 : https://stackoverflow.com/a/54494216

#!/bin/sh

get_up()
{
        read -r UP REST </proc/uptime
        export UP=${UP%.*}${UP#*.}
}

wait_till_1sec_is_full()
{
    while true; do
        get_up
        test $((UP-START)) -ge 100 && break
    done
}

while true; do
    get_up; START=$UP

    your_code

    wait_till_1sec_is_full
done
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.