blockierten thread beenden/weiter laufen lassen
-
hi!
ich hab immmer wieder folgendes problem:
ein thread blockiert und wartet (z.B accept bei sockets, read bei files ect.).
dann kommt jedoch ein signal oder ein anderer thread bekommt neue informationen, und muss das programm oder nur den wartenden thread weiterlaufen lassen oder beenden (je nachdem, bei files oftmals einfach neues file versuchen ect.).wie kann ich den wartenden thread "unblocken"? es gibt ja bei semaphoren ein "notify all", aber gilt das auch in meinem fall, wo ich ja durch einen anderen prozess/thread/kernel geblockt werde?
was für ein sys-call kommt in frage?ich bin dankbar für jeden ansatz/tipp.. ich glaub ich hab mich beim drüber grübeln verlaufen
-
1. select wäre eine Option
2. Die Anforderungen hab ich nicht verstanden. Kannst du das nochmal präzisieren und ausführen, wann was wie läuft?
-
Blockierende Systemroutinen brechen bei der Ankunft eines Signals generell mit dem Fehlercode EINTR ab.
Bei Linux ist es so, dass ein Signal immer an allen Threads des Prozesses ankommt, und daher alle wartenden Systemroutinen in allen Threads unterbricht. Dies kann man dazu benutzen, z.B. mit alarm(2) einen Timer zu setzen, der nach X Sekunden ein SIGALRM-Signal an den Prozess schickt (bei Linux 2.4: Prozessgruppe, da jeder Thread ein Prozess ist). Mit sigaction(2) kann man fuer verschiedene Signale Signalhandler setzen. (mit "man 2 sigaction" kannst Du die Man-Page der Funktion lesen)
Hilfe zu den Signalen kriegst Du mittels "info SIGALRM", z.B.
Man kann das Signal SIGIO fuer einen File-Deskriptor (auch Sockets etc.) einschalten, indem man mittels fcntl(2) das Flag O_ASYNC setzt. Dann werden blockierende Systemaufrufe mit EINTR abbrechen, wenn das SIGIO Signal zugestellt wird (bei anderen UNIX-Systemen heisst das Signal SIGPOLL, bei Linux sind SIGIO und SIGPOLL identisch). Das SIGIO-Signal wird zugestellt, wenn z.B. bei einem Socket eine neue Verbindung ansteht. D.h. man kann, wenn man vorher mit select(2) geprueft hat, ob am Listener eine Verbindung anliegt, dann das blockierende accept(2) aufrufen, ohne dass es blockiert.
Man muss aber dran denken, dass jeder blockierende I/O-Zugriff abgebrochen werden kann mit EINTR, und dass man dann natuerlich den Zugriff nochmal probieren muss (das gilt auch fuer scheinbar harmlose Aufrufe wie printf(3)!!).
Die Alternative ist, einen Socket oder ein Filehandle auf Nonblocking-I/O zu schalten, indem man mittels fcntl(2) das Flag O_NONBLOCK setzt. Dann brechen Aufrufe sofort ab, wenn sie blockieren wuerden, mit dem Fehlercode EAGAIN.
Aber man muss trotzdem dran denken, dass jeder Systemaufruf, bei dem das dokumentiert ist, EINTR als Fehler melden kann, und dass man dann den Aufruf nochmal probieren muss.
Will man keine Signalhandler schreiben, kann man mittels sigaction(2) die Signale ignorieren (sa_handler auf SIG_IGN setzen).
Etliche Signale sind aber lebenswichtig, gerade beim Multithreading. Meines Wissens benutzt LinuxThreads (pthread-Library) die Signale SIGUSR1 und SIGUSR2.
Am besten schreibt man sich Wrapper, die die einzelnen Systemaufrufe kapseln und ein sauberes Error-Handling machen.
-
hmm. das war ja sehr ausführlich, danke!
Zum verständnis für mich,würde das funktionieren?:- Ich registriere mich für SIGINT (bin ich schon!)
- drei threads starten und blockieren (file, socket, IPC,...).
- Ich bekomme ein signal SIGINT und behandle es. alle drei threads laufen
automatisch weiter, nachdem der SIGINT-handler beendet ist? (okay, nach SIGINT ist ja dann sowieso alles aus, aber man denke sich ein signal wo alles weiterlaeuft)
Frage: Wo laufen denn die threads weiter (welcher einstiegspunkt/addresse?)? direkt nach dem letzen blockierenden aufruf? oder blockieren die sofort wieder an der gleichen stelle? Fragen über Fragen!!!
danke aber schonmal für deine ausführliche "erste hilfe"!
-
Da bei Ankunft von SIGINT die blockierenden Aufrufe mit Fehlercode EINTR abbrechen, laufen die Threads an dieser Stelle weiter, wenn der Signal-Handler beendet ist.
SIGINT (Ctrl-C vom Terminal) bewirkt nicht automatisch, dass der Prozess beendet wird. Wenn Du im Signalhandler _Exit(2) aufrufst, kannst Du das Programm selbst beenden. Wenn Du im Signalhandler nichts machst, wird das Signal effektiv ignoriert. D.h. was passiert, haengt nur von Deinem Signal-Handler ab. Du kannst auch mittels longjmp(3) aus dem Signalhandler in Deinen Code springen, um z.B. an zentraler Stelle wiederaufzusetzen (Du musst man mit getpid(2) bzw. gettid(2) pruefen, fuer welchen Thread das Signal bestimmt war, und kannst danach entscheiden, wohin Du springst). Oder Du setzt einfach ein Flag, das Du pruefst, wenn EINTR von einem blockierenden Aufruf gemeldet wird.
Alle Signale ausser SIGKILL koennen mittels sigaction(2) abgefangen werden.
-
Power Off schrieb:
Bei Linux ist es so, dass ein Signal immer an allen Threads des Prozesses ankommt
Das kann nicht stimmen. Eigentlich sollte es nur bei einem Thread ankommen. Und für ALARM gilt es ganz sicher nicht, das geht immer an den Thread, der alarm() aufgerufen hat.
-
ja, alles sehr schön, aber was kann ich mit EINTR denn sinnvolles machen?
ich will den thread doch eigentlich nur wieder aufwecken-- aus dem kernel heraus kann ich das doch auch.. warum nicht von meiner eigenen application aus?also wie kann man das machen? ich muss doch einen thread stoppen/ weiterlaufen lassen können!
Test:
-socket angelegt
-auf accept gewartet (geblockt)
-signal geschickt.
-->nix passiert- thread ist weiterhin bei accept geblockt.
-Ziel: accept abbrechen, und weiterlaufen... muss doch möglich sein!*arghh*
-
Gehirnmann schrieb:
ja, alles sehr schön, aber was kann ich mit EINTR denn sinnvolles machen?
ich will den thread doch eigentlich nur wieder aufwecken-- aus dem kernel heraus kann ich das doch auch.. warum nicht von meiner eigenen application aus?also wie kann man das machen? ich muss doch einen thread stoppen/ weiterlaufen lassen können!
Test:
-socket angelegt
-auf accept gewartet (geblockt)
-signal geschickt.
-->nix passiert- thread ist weiterhin bei accept geblockt.
-Ziel: accept abbrechen, und weiterlaufen... muss doch möglich sein!*arghh*
Hab ich dir doch gesagt: select
-
Es kommt auch drauf an, was beim Signal Handler (per sigaction) für Flags gesetzt sind. Da gibt's irgendein RESTART-Flag, der das Abbrechen mit EINTR verhindert.
-
Ringding schrieb:
Power Off schrieb:
Bei Linux ist es so, dass ein Signal immer an allen Threads des Prozesses ankommt
Das kann nicht stimmen. Eigentlich sollte es nur bei einem Thread ankommen. Und für ALARM gilt es ganz sicher nicht, das geht immer an den Thread, der alarm() aufgerufen hat.
Probier mal das hier aus:
#include <signal.h> #include <unistd.h> #include <pthread.h> int flg = 0; static void hangup_handler( int sig ) { const char* s = "???"; if ( sig == SIGHUP ) s = "SIGHUP"; printf( "hangup_handler(): called, sig = %s (%d); pid = %u\n", s, sig, getpid() ); flg = 1; } void Init_SigHangUpHandler( void ) { struct sigaction act; memset( &act, 0, sizeof(act) ); act.sa_handler = hangup_handler; sigemptyset( &act.sa_mask ); sigaction( SIGHUP, &act, 0 ); } void Cleanup_SigHangUpHandler( void ) { struct sigaction act; memset( &act, 0, sizeof(act) ); act.sa_handler = SIG_DFL; sigemptyset( &act.sa_mask ); sigaction( SIGHUP, &act, 0 ); } static void term_handler( int sig ) { const char* s = "???"; if ( sig == SIGTERM ) s = "SIGTERM"; printf( "term_handler(): called, sig = %s (%d); pid = %u\n", s, sig, getpid() ); flg = 1; } void Init_SigTermHandler( void ) { struct sigaction act; memset( &act, 0, sizeof(act) ); act.sa_handler = term_handler; sigemptyset( &act.sa_mask ); sigaction( SIGTERM, &act, 0 ); } void Cleanup_SigTermHandler( void ) { struct sigaction act; memset( &act, 0, sizeof(act) ); act.sa_handler = SIG_DFL; sigemptyset( &act.sa_mask ); sigaction( SIGTERM, &act, 0 ); } void* Thread_Func( void* arg ) { printf( "hello from thread 2 (pid %u)\n", getpid() ); Init_SigTermHandler(); for (;;) { sleep( 1 ); if ( flg ) break; } Cleanup_SigTermHandler(); printf( "goodbye from thread 2\n" ); return 0; } int main( int argc, char** argv ) { printf( "hello from main program (pid %u)\n", getpid() ); Init_SigHangUpHandler(); pthread_t thr1 = 0; pthread_create( &thr1, 0, Thread_Func, 0 ); for (;;) { sleep( 1 ); if ( flg ) break; } void* thr1_ret = 0; pthread_join( thr1, &thr1_ret ); Cleanup_SigHangUpHandler(); printf( "goodbye from main program\n" ); return 0; }
Starte das Programm in einer Shell, und schick dann Signale an jeweils den einen oder anderen Thread. (mit 2.4 Kernel, bei 2.6 haben beide Threads die gleiche ID)
Du wirst feststellen, dass es egal ist, wo der Signal-Handler definiert wird. D.h. der Thread, der SIGTERM registriert, verarbeitet auch SIGHUP, und umgekehrt.
Q.E.D.
-
ich hab die antwort zufällig in einem anderen thread gefunden:
also das -EINTR wird von den blockenden funktionen erzeugt- als rückgabe.
jetzt ist mir auch klar warum ihr meine frage nicht verstanden habtdanke nochmal!
jetzt geht alles!-> blumen zum frühstück, dann klappts auch mit den nachbarn!