2015-06-21 11 views
5

Ich frage mich, wie ist Schlaf/Nanoschlaf intern implementiert? Betrachten Sie diesen Code:Funktioniert Sleep/NanoSleep mit einem "busy wait" -Schema?

{ // on a thread other than main() thread 
    while(1) 
    { 
    //do something 
    sleep(1); 
    } 
} 

würde die CPU konstant Kontext tun Umschalten zu überprüfen, ob Schlaf von 1 s durchgeführt wird (das heißt eine interne busy wait).

Ich bezweifle, dass es auf diese Weise funktioniert, zu viel Ineffizienz. Aber wie funktioniert es?

Die gleiche Frage gilt für Nanosleep.

Hinweis: Wenn dies Implementierungs-/Betriebssystemspezifisch ist, wie kann ich dann möglicherweise ein effizienteres Schema implementieren, das nicht zu einem ständigen Kontextwechsel führt?

+1

Was ist los mit der Frage? – Kam

+0

_ "Was ist los mit der Frage?" _ Zu breit. Betriebssystemspezifisch, implementierungsspezifisch. –

+0

POSIX legt nicht fest, wie es implementiert werden soll – StenSoft

Antwort

2

Genaue Implementierung ist hier nicht garantiert, aber Sie können einige Eigenschaften erwarten.

Normalerweise sleep (3) ist ziemlich ungenau und wie Linux 'Mann Schlaf 3' Zustände könnten sogar mit SIGALM (Signale) implementiert werden. Es geht also definitiv nicht um Leistung. Es geht definitiv auch nicht um Spin-Locks, also kann es nicht CPU-intensiv sein.

nanosleep ist ein ziemlich anderes Tier, das sogar mit Spinlocks implementiert werden könnte. Was ist wichtiger, zumindest in Linux nanosleep Mann ist in Abschnitt 2, die es steht Systemaufruf, so sollte es zumindest Switch to Kernel-Modus enthalten. Brauchen Sie wirklich seine hohe Auflösung?

UPDATE

Wie ich sehe, Ihren Kommentar, den ich select() Nutzung als man select 3 Staaten tun empfehlen:

#include <stdio.h> 
    #include <stdlib.h> 
    #include <sys/time.h> 
    #include <sys/types.h> 
    #include <unistd.h> 

    int 
    main(void) 
    { 
     fd_set rfds; 
     struct timeval tv; 
     int retval; 

     /* Watch stdin (fd 0) to see when it has input. */ 
     FD_ZERO(&rfds); 
     FD_SET(0, &rfds); 

     /* Wait up to five seconds. */ 
     tv.tv_sec = 5; 
     tv.tv_usec = 0; 

     retval = select(1, &rfds, NULL, NULL, &tv); 
     /* Don't rely on the value of tv now! */ 

     if (retval == -1) 
      perror("select()"); 
     else if (retval) 
      printf("Data is available now.\n"); 
      /* FD_ISSET(0, &rfds) will be true. */ 
     else 
      printf("No data within five seconds.\n"); 

     exit(EXIT_SUCCESS); 
    } 

Es Mechanik bewiesen ist, wenn man im Thread auf ein Ereignis, und dieses Ereignis könnte zum Schlafen mit dem Dateideskriptor verknüpft sein.

+0

Ich bin nicht auf der Suche nach hoher Auflösung, ich bin mehr besorgt über die Leistung meiner anderen Threads im ständigen Kontextwechsel, weil ich einen Thread habe, der 99,9% der Zeit (mit Schlaf (30)) schläft – Kam

+0

30 Sekunden ist eine Ewigkeit, können Sie sicher den Thread CPU-Quanten mit Schlaf von sogar wegwerfen ein paar Millisekunden, ohne dass das System es bemerkt hat. –

+2

Hinweis: Diese Frage wird derzeit als [Tag: Posix], nicht [Tag: Linux] markiert. In POSIX kann 'nanosleep' nicht mit Spin Locks implementiert werden. Der Thread muss angehalten werden. Ob das bei Linux der Fall ist, ist eine andere Frage, denn Linux ist nicht POSIX-konform. –

0

"Ich frage mich, wie ist Schlaf/Nanoschlaf intern implementiert?"

Es gibt nicht die eine Implementierung für sie, aber jedes OS und POSIX-konforme Implementierung von sleep() und nanosleep() sind frei, wie sie diese Funktion tatsächlich umzusetzen.

So fragen, wie es tatsächlich getan wird, ist ziemlich nutzlos, ohne mehr Kontext einer bestimmten OS/POSIX-Bibliothek-Implementierung.

1

Die POSIX-Spezifikation von sleep und nanosleep sagen (emphasis Mine)

Der Schlaf() Funktion wird der anrufende Faden verursachen von bis sie durch das Argument angegeben entweder die Anzahl der Echtzeit-Sekunden Ausführung ausgesetzt werden Sekunden verstrichen sind oder ein Signal an den aufrufenden Thread geliefert wird, und seine Aktion besteht darin, eine Signalfangfunktion aufzurufen oder den Prozess zu beenden. Die Aussetzungszeit kann aufgrund der Planung anderer Aktivitäten durch das System länger als gewünscht sein.

(Quelle:. http://pubs.opengroup.org/onlinepubs/9699919799/functions/sleep.html)

und

Die nanosleep() Funktion wird der aktuelle Thread verursacht von der Ausführung ausgesetzt werden bis entweder das Zeitintervall vom angegebenen Das rqtp-Argument ist abgelaufen oder ein Signal wird an den aufrufenden Thread geliefert, und seine Aktion besteht darin, eine Signalfangfunktion aufzurufen oder den Prozess zu beenden. Die Aussetzungszeit kann länger als angefordert sein, da der Argumentwert auf ein ganzzahliges Vielfaches der Schlafauflösung aufgerundet wird oder weil andere Aktivitäten vom System geplant werden. Außer im Falle der Unterbrechung durch ein Signal darf die Aussetzzeit jedoch nicht kleiner sein als die von rqtp angegebene Zeit, gemessen mit der Systemuhr CLOCK_REALTIME.

(Quelle:. http://pubs.opengroup.org/onlinepubs/9699919799/functions/nanosleep.html)

las ich, dass zu sagen, dass ein POSIX-konformes System nicht besetzt Schleife für sleep oder nanosleep verwenden kann. Der aufrufende Thread muss von der Ausführung angehalten werden.

3

Der typische Weg zur Implementierung sleep() und nanosleep() besteht darin, das Argument in eine beliebige Skalierung des OS-Schedulers zu konvertieren (während der Abrundung) und die aktuelle Zeit hinzuzufügen, um eine "absolute Aufwachzeit" zu bilden; Dann sagen Sie dem Scheduler, dem Thread keine CPU-Zeit zu geben, bis die "absolute Aufwachzeit" erreicht ist. Kein beschäftigtes Warten ist beteiligt.

Beachten Sie, dass die Skalierung des OS-Schedulers normalerweise davon abhängt, welche Hardware verfügbar ist und/oder für die Zeitmessung verwendet wird. Sie kann kleiner als eine Nanosekunde sein (z. B. lokale APIC auf 80 · 86, die im "TSC-Terminierungsmodus" verwendet wird) oder so groß wie 100 ms.

Beachten Sie auch, dass das Betriebssystem garantiert, dass die Verzögerung nicht weniger als das ist, was Sie verlangen; aber es gibt normalerweise keine Garantie, dass es nicht länger sein wird und in einigen Fällen (z. B. Thread mit niedriger Priorität in einem stark ausgelasteten System) kann die Verzögerung viel viel größer als gewünscht sein. Wenn Sie zum Beispiel für 123 ns bitten zu schlafen, dann können Sie für 2 ms schlafen, bevor der Scheduler entscheidet, dass es Ihnen CPU-Zeit geben kann, und dann kann es weitere 500 ms geben, bevor der Scheduler Ihnen CPU-Zeit gibt (zB weil andere Threads verwenden die CPU).

Einige Betriebssysteme versuchen möglicherweise, dieses "länger geschlafen als angefordert" -Problem zu reduzieren, und einige Betriebssysteme (z. B. für hard-real-time ausgelegt) bieten möglicherweise eine Art Garantie (mit Einschränkungen - z. B. Thread-Priorität) Mindestzeit zwischen dem Ablauf der Verzögerung und dem Zurückholen der CPU. Um dies zu tun, würde das OS/kernel das Argument in jede Skalierung umwandeln, die der Scheduler des OS benutzt (während Abrundung und nicht Abrundung) und eine kleine Menge "nur für den Fall" subtrahieren kann; damit der Scheduler den Thread kurz vor Ablauf der angeforderten Verzögerung aufwacht (und nicht danach); und dann, wenn dem Thread CPU-Zeit gegeben wird (nachdem die Kosten des Kontextwechsels zum Thread und möglicherweise nach dem Vorabholen verschiedener Cache-Zeilen den Thread garantiert verwenden), würde der Kernel beschäftigt sein, kurz zu warten, bis die Verzögerung tatsächlich abgelaufen ist. Dies ermöglicht es dem Kernel, die Kontrolle wieder an den Thread zu übergeben, der dem Ablauf der Verzögerung extrem nahe kommt. Wenn Sie zum Beispiel für 123 ns bitten zu schlafen, gibt Ihnen der Scheduler möglicherweise keine CPU-Zeit für 100 ns, dann kann er 10 ns zu Ihrem Thread wechseln, dann wartet er möglicherweise auf die verbleibenden 13 ns. Selbst in diesem Fall (in dem das Besetztzeichen ausgeführt wird) wird normalerweise nicht auf die volle Dauer der Verzögerung gewartet.Wenn die Verzögerung jedoch extrem kurz ist, würde der Kernel nur das letzte busy-Warten durchführen.

Schließlich gibt es einen speziellen Fall, der erwähnenswert ist. Bei POSIX-Systemen wird sleep(0); normalerweise als yield() missbraucht. Ich bin nicht sicher, wie legitim diese Praxis ist - es ist unmöglich für einen Scheduler, etwas wie yield() zu unterstützen, es sei denn, der Scheduler ist bereit, CPU-Zeit für unwichtige Arbeit zu verschwenden, während wichtigere Arbeit wartet.