วิ่งวนซ้ำอย่างแม่นยำหนึ่งครั้งต่อวินาที


33

ฉันใช้วงนี้เพื่อตรวจสอบและพิมพ์บางสิ่งทุกวินาที อย่างไรก็ตามเนื่องจากการคำนวณอาจใช้เวลาสักสองสามร้อยมิลลิวินาทีเวลาที่พิมพ์จึงข้ามหนึ่งวินาที

มีวิธีใดที่จะเขียนลูปที่ฉันรับประกันว่าจะได้รับผลงานพิมพ์ทุกวินาทีหรือไม่? (แน่นอนว่าการคำนวณในลูปนั้นใช้เวลาน้อยกว่าหนึ่งวินาที :))

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
โปรดทราบว่า " ได้อย่างแม่นยำหนึ่งครั้งต่อวินาที" เป็นไปไม่ได้อย่างแท้จริงในกรณีส่วนใหญ่เพราะคุณ (ปกติ) ทำงานใน userspace บนเคอร์เนล preemptively มัลติทาสกิ้งซึ่งจะกำหนดรหัสของคุณตามที่เห็นสมควร (ดังนั้นที่คุณอาจไม่สามารถควบคุมได้ทันทีหลังจากการนอนหลับ ยกตัวอย่างเช่น) ยกเว้นว่าคุณกำลังเขียนรหัส C ซึ่งเรียกใช้sched(7)API (POSIX: ดู<sched.h>และหน้าเว็บที่เชื่อมโยงจากที่นั่น) โดยทั่วไปคุณจะไม่สามารถรับประกันแบบฟอร์มตามเวลาจริงได้
เควิน

เพียงแค่สำรองข้อมูลที่ @Kevin พูดไว้การใช้โหมด sleep () เพื่อลองและกำหนดเวลาที่แม่นยำจะทำให้เกิดความล้มเหลวเพียงแค่รับประกันว่าจะนอนอย่างน้อย 1 วินาที หากคุณต้องการการกำหนดเวลาที่แม่นยำอย่างแท้จริงคุณต้องดูที่นาฬิการะบบ (ดู CLOCK_MONOTONIC) และทริกเกอร์สิ่งต่าง ๆ ตามเวลานับตั้งแต่ครั้งสุดท้ายของเหตุการณ์ + 1s และทำให้แน่ใจว่าคุณไม่ได้เดินทางด้วยตัวเอง> 1 วินาทีเพื่อให้ทำงาน การคำนวณในครั้งต่อไปหลังจากการดำเนินการบางอย่างเป็นต้น
John U

เพียงออกจากที่นี่falsehoodsabouttime.com
alo Malbarez

แม่นยำหนึ่งครั้งต่อวินาที = ใช้ VCXO โซลูชันเฉพาะซอฟต์แวร์จะช่วยให้คุณ "ดีพอ" แต่ไม่แม่นยำ
Ian MacDonald

คำตอบ:


65

หากต้องการอยู่ใกล้กับรหัสเดิมมากขึ้นสิ่งที่ฉันทำคือ:

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

สิ่งนี้เปลี่ยนความหมายเล็กน้อย: ถ้าสิ่งของของคุณใช้เวลาน้อยกว่าหนึ่งวินาทีมันก็จะรอให้วินาทีเต็มผ่านไป อย่างไรก็ตามหากเนื้อหาของคุณใช้เวลานานกว่าหนึ่งวินาทีไม่ว่าจะด้วยเหตุผลใดก็ตามมันจะไม่ทำให้เกิดการประมวลผลย่อยมากยิ่งขึ้นโดยไม่สิ้นสุด

ดังนั้นสิ่งของของคุณจะไม่ทำงานคู่ขนานกันและไม่อยู่ในพื้นหลังดังนั้นตัวแปรจึงทำงานตามที่คาดไว้เช่นกัน

โปรดทราบว่าหากคุณเริ่มงานพื้นหลังเพิ่มเติมเช่นกันคุณจะต้องเปลี่ยน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โดยการใช้งานของnaïve (เพราะนั่นคือสิ่งที่atoiจะทำ) ไม่แน่ใจว่าจะส่งผลให้เกิดข้อผิดพลาดหรือไม่
jcaron

1
ฉันดีใจที่ได้เห็นคำถามนี้ก่อให้เกิดความสนใจอย่างมาก ข้อเสนอแนะและคำตอบของคุณดีมาก ไม่เพียง แต่รักษาไว้ภายในวินาทีเท่านั้น แต่ยังติดให้ใกล้เคียงกับวินาทีมากที่สุดเท่าที่จะทำได้ ที่น่าประทับใจ! (PS! ในหมายเหตุด้านหนึ่งจะต้องติดตั้ง GNU Coreutils และใช้gdateกับ macOS เพื่อdate +%Nทำงาน)
outrin

30

หากคุณสามารถปรับโครงสร้างห่วงของคุณลงในสคริปต์ / oneliner แล้ววิธีที่ง่ายที่สุดที่จะทำนี้ด้วยwatchและpreciseตัวเลือก

คุณสามารถเห็นเอฟเฟกต์ด้วยwatch -n 1 sleep 0.5- มันจะแสดงจำนวนวินาทีนับ แต่จะข้ามไปเป็นครั้งคราว วิ่งตามที่watch -n 1 -p sleep 0.5จะส่งออกสองครั้งต่อวินาทีทุกวินาทีและคุณจะไม่เห็นข้ามใด ๆ


11

ทำงานการดำเนินงานใน subshell 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

ครั้งเดียวที่ "ถูกขโมย" จากวินาทีเดียวจะเป็นเวลาที่ใช้ในการเริ่มการ subshell ดังนั้นในที่สุดมันก็จะข้ามวินาที แต่หวังว่าจะน้อยกว่ารหัสเดิม

ถ้ารหัสใน subshell ปลายขึ้นโดยใช้มากขึ้นกว่าสองวงจะเริ่มงานพื้นหลังสะสมและในที่สุดก็วิ่งออกมาจากทรัพยากร


9

ทางเลือกอื่น (หากคุณไม่สามารถใช้งานได้เช่นwatch -pตามคำแนะนำของ Maelstrom) คือsleepenh[ manpage ] ซึ่งออกแบบมาสำหรับสิ่งนี้

ตัวอย่าง:

#!/bin/sh

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

สังเกตว่าsleep 0.2มีการจำลองการทำงานที่กินเวลาประมาณ 200ms แม้จะมีผลลัพธ์นาโนวินาทียังคงมีเสถียรภาพ (ดีตามมาตรฐานระบบปฏิบัติการที่ไม่ใช่เรียลไทม์) - มันเกิดขึ้นหนึ่งครั้งต่อวินาที:

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

มันต่ำกว่า 1 มิลลิวินาทีและไม่มีเทรนด์ ค่อนข้างดี คุณควรคาดหวังการตีกลับอย่างน้อย 10ms หากมีการโหลดใด ๆ ในระบบ - แต่ก็ยังไม่มีลอยในช่วงเวลา คือคุณจะไม่สูญเสียวินาที


7

ด้วยzsh:

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

ถ้านอนหลับของคุณไม่สนับสนุนลอยวินาทีจุดที่คุณสามารถใช้zsh's zselectแทน (หลังจากที่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

สิ่งเหล่านั้นไม่ควรลอยหากคำสั่งในลูปใช้เวลาน้อยกว่าหนึ่งวินาทีในการเรียกใช้


0

ฉันมีข้อกำหนดเดียวกันที่แน่นอนสำหรับสคริปต์เชลล์ POSIX ซึ่งผู้ช่วยเหลือทั้งหมด (usleep, GNUsleep, sleepenh, ... ) ไม่สามารถใช้ได้

ดู: 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.