Tam olarak saniyede bir kez döngü oluşturma


33

Her saniye bazı şeyleri kontrol etmek ve yazdırmak için bu döngüyü çalıştırıyorum. Ancak, hesaplamalar birkaç yüz milisaniye sürdüğü için, yazdırılan süre bazen bir saniye atlar.

Her saniye bir çıktı almamın garantisi olduğu böyle bir döngü yazmamın bir yolu var mı? (Elbette, döngü içindeki hesapların bir saniyeden daha az sürmesi sağlanır :))

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


26
" Tam olarak saniyede bir kez" nin çoğu durumda gerçek anlamda mümkün olmadığını unutmayın çünkü çoğu zaman (genellikle) kodunuzu uygun gördüğü şekilde zamanlayabilecek (örneğin bir uykudan hemen sonra kontrolü elinize almazsınız) ön plana çıkaran çok görevli bir çekirdeğin üstündeki kullanıcı alanında çalışıyorsunuzdur. örneğin biter). sched(7)API'yi çağıran C kodu yazmıyorsanız (POSIX: bkz <sched.h>ve oradan bağlantılı sayfalar), temel olarak bu formun gerçek zamanlı garantilerine sahip olamazsınız.
Kevin,

@Kevin'in söylediklerini yedeklemek için, her türlü kesin zamanlamayı denemek ve almak için sleep () işlevinin başarısızlığa mahkum olması, sadece AT LEAST 1sec uyku garantisi verir. Eğer varsa gerçekten hassas zamanlamalarını ihtiyaç sen> 1sec çalıştırmak alarak kendini çelme yok zamana beri-son-etkinliğin + 1'ler dayalı (CLOCK_MONOTONIC bakınız) ve tetik şeyler sistem saatinin bakmak gerekiyor ve emin olun bir dahaki sefere hesaplanırken sonra bazı operasyon, vb
John U

Sadece burada bu bırakacak falsehoodsabouttime.com
Malbarez alo

Tam olarak saniyede bir = VCXO kullanın. Yalnızca yazılım çözümü yalnızca "yeterince iyi" olur, ancak kesin değildir.
Ian MacDonald

Yanıtlar:


65

Orijinal koda biraz daha yakın kalmak için yaptığım şey:

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

Bu, anlambilimi biraz değiştirir: eğer eşyaların bir saniyeden az sürerse, tam saniyenin geçmesini bekler. Ancak, herhangi bir nedenden ötürü eşyalarınız bir saniyeden uzun sürerse, hiçbir zaman son bulmadan daha fazla alt işlemi üretmeye devam etmez.

Böylece eşyaların asla paralel olarak çalışmaz ve arka planda olmaz, bu yüzden değişkenler de beklendiği gibi çalışır.

Ek arka plan görevlerini de başlatırsanız, waittalimatı yalnızca sleepişlemi özel olarak beklemek için değiştirmek zorunda kalacağınızı unutmayın .

Daha doğru olması gerekirse, muhtemelen sadece sistem saatiyle senkronize etmeniz ve tam saniye yerine ms uyumanız gerekecektir.


Sistem saati ile nasıl senkronize edilir? Hiçbir fikrim yok, aptalca bir girişim:

Varsayılan:

while sleep 1
do
    date +%N
done

Çıktı: 003511461 010510925 016081282 021643477 028504349 03 ... (büyümeye devam ediyor)

Senkronize:

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

Çıktı: 002648691 001098397 002514348 001293023 001679137 00 ... (aynı kalır)


9
Bu uyku / bekleme numarası gerçekten zeki!
philfr

Merak ediyorum, bütün sleeptutamaçlı saniye uygulamalarının işlenip uygulanmadığını ?
Jcaron

1
@jcaron hepsi değil. Ancak, gnu uyku ve meşgul kutusu uyku için çalışıyor bu yüzden egzotik değil. Muhtemelen sleep 0.9 || sleep 1geçersiz parametre gibi basit bir geri dönüş yapabilirdi uyku neredeyse başarısız tek nedenidir.
frostschutz

@frostschutz naif uygulamalarla sleep 0.9yorumlanmasını beklerdim sleep 0(ne atoiyapacağı belli olur). Bunun bir hatayla sonuçlanıp sonuçlanmadığından emin değilim.
Jcaron

1
Bu sorunun ilgisini çektiği için mutluyum. Öneriniz ve cevabınız çok iyi. Sadece saniyenin içinde kalmasını sağlamakla kalmaz, aynı zamanda tüm saniyeye olabildiğince yakın kalır. Etkileyici! (Not! Bir yandan not etmek gerekirse, GNU Coreutils'i kurmalı ve çalışmak gdateiçin macOS'ta kullanmalısınız date +%N.)
18'de

30

Döngünüzü bir betik / oneliner'da yeniden yapılandırabilirseniz, bunu yapmanın en basit yolu watchve onunla preciseseçenek.

Etkisi ile görebilirsiniz watch -n 1 sleep 0.5- saniye saymayı gösterir, ancak bazen bir saniyeden fazla atlar. Olarak Koşu watch -n 1 -p sleep 0.5saniyede iki kez irade çıkışı, her saniyesinde ve herhangi atlar görmezsiniz.


11

İşlemleri, arka plan işi olarak çalışan bir alt kabukta çalıştırmak, onları çok fazla etkilememesini sağlar 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

Bir saniyeden “çalınan” tek zaman, denizaltıyı başlatmak için harcanan zaman olacaktır, bu yüzden sonunda bir saniye atlayacaktır, umarım orijinal koddan daha az sıklıkla.

Alt kabuktaki kod bir saniyeden fazla sürerse, döngü arka plan işlerini biriktirmeye başlar ve sonunda kaynaklar tükenir.


9

Başka bir alternatif (örneğin, watch -pMaelstrom'un önerdiği gibi kullanamazsanız ) bunun için tasarlanmış sleepenh[ manpage ].

Örnek:

#!/bin/sh

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

Not sleep 0.2200ms etrafında yeme biraz zaman alıcı bir görev yapıyor orada simüle. Buna rağmen, nanosaniye çıkış kararlı kalır (gerçek zamanlı olmayan işletim sistemi standartlarına göre) - saniyede bir olur:

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

Bu 1ms'in altında farklı ve trend yok. Bu oldukça iyi; sistemde herhangi bir yük varsa - ancak zamanla kayma olmadığında en az 10ms'lik bir sıçrama beklemelisiniz. Yani, bir saniye kaybetmeyeceksin.


7

İle zsh:

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

Uyku noktası saniye kayan desteklemiyorsa, kullanabileceğiniz zsh's zselect(a sonra yerine 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

Bunlar döngüdeki komutların çalışması bir saniyeden az sürdüğü sürece kaymamalıdır.


0

Tüm yardımcıların (usleep, GNUsleep, sleepenh, ...) kullanılamadığı bir POSIX kabuk betiği için de aynı gereksinime sahibim.

bakınız: 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.