PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Benutzung und Auswertung von void **retval in pthread_join()



HaWe
23.10.2016, 09:09
hallo,
meine Frage zu
" Benutzung und Auswertung von void **retval "
ist zwar generell zu C, bezieht sich aber auf ein konkretes Problem bei der Raspi-Programmierung, daher stelle ich sie hier.

Gebraucht werden in einem Programm mehrere pthreads, die folgendermaßen verwaltet werden:



long f;
pthread_t threadID0, threadID1, threadID2;


int main() {
void **retval0 = NULL, **retval1 = NULL, **retval2 = NULL;

printf("enter order: ");
scanf("%ld", &f);

pthread_create( &threadID0, 0, threadW0, 0 );
pthread_create( &threadID1, 0, threadf1, 0 );
pthread_create( &threadID2, 0, threadf2, 0 );


pthread_join( threadID0, retval0 );
pthread_join( threadID1, retval1 );
pthread_join( threadID2, retval2 );

printf("\nprogram end \n");
return 0;
}



ich möchte nun die Rückgabewerte (retval) von pthread_join auswerten, um festzustellen, ob die pthreads sich selber beendet haben (per eigener Abbruchbedingung) oder ob einer der pthreads per
pthread_cancel
von einem anderen thread aus "zwangsweise" beendet wurde.

wie frage ich aber die Werte von retval ab? Es sind ja keine Integerwerte, die man z.B. auf 0 oder 3 testen kann, sondern void** (ich lese das als einen Pointer auf die Adresse einer typenlosen Variablen)
- wie händelt man das?

markusj
23.10.2016, 10:12
Du kannst es auch etwas anders betrachten, hier nochmal die Signatur von pthread_create:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Oder etwas umformuliert:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
out_t *(*start_routine) (in_t *), in_t *arg);

start_routine ist also eine Funktion, die einen Pointer auf etwas (in_t) bekommt, und später einen Pointer auf etwas anderes (out_t) zurück gibt. Wie verhält sich pthread_join dazu? Nun, in der obigen Darstellung würde pthread_join so aussehen:

int pthread_join(pthread_t thread, out_t **retval);

Du hast also irgendwo einen Pointer A, der später auf das Ergebnis zeigen soll. Und übergibst einen Pointer auf diesen Pointer A, damit pthread_join den Wert von Pointer A entsprechend setzt.

Randnotiz: Du kannst den Rückgabewert nicht ohne vorherige Überprüfung nutzen, näheres dazu verrät die Doku (https://linux.die.net/man/3/pthread_join)

Grüße,
Markus

HaWe
23.10.2016, 11:02
ok, danke -
die man page pthread_join(3) kenne ich, aber schlauer war ich daraus auch nicht geworden. Dort heißt es ja gerade

If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the value that the target thread supplied to pthread_exit(3)) into the location pointed to by *retval. If the target thread was canceled, then PTHREAD_CANCELED is placed in *retval.

Zum Einen benutze ich nun kein pthread_exit((), sondern ausschließlich pthread_cancel().
Zum Anderen: was genau muss ich tun, um retval auswerten zu können? Ich möchte ja gerade wissen, ob irgendwo was anderes als der Nullpointer steht. Genau diese Info fehlt mir ja noch.

markusj
23.10.2016, 14:48
Nun, um auf den ersten Teil deiner Frage zu antworten: Die Antwort steht in dem Zitat. "if the target thread was canceled, then PTHREAD_CANCELED is placed in *retval". In dem Fall gilt also *retval == PTHREAD_CANCELED. Ansonsten hat sich der Thread regulär beendet und du kannst über retval den Rückgabewert auslesen. Wie die Doku zu pthread_exit() verrät, musst du diese Funktion übrigens nicht explizit aufrufen. Ein "return foo;" führt implizit zu einem entsprechenden Aufruf von pthread_exit().

Grüße,
Markus

HaWe
23.10.2016, 15:25
das ist schon klar, aber was ist z.B. *retval bzw. PTHREAD_CANCELED für ein Datentyp? Ein Integerwert, den ich genau wie NULL als defaultwert z.B. ausdrucken könnte mit
printf("%d", *retval);
?
das funktioniert nämlich nicht.
Auch als exit code von main() funktioniert so etwas wie

if(*retval1 !=NULL) return 1;
else
return 0;

ebenfalls nicht.
Auch wenn ich stattdessen schreibe


if(*retval1 ==PTHREAD_CANCELED) return 1;
else
return 0;

funktioniert die Abfrage nicht, in diesen Fällen ist der exit code 139, nicht 1.

Also wie kann ich retvals z.B. mit printf korrekt ausdrucken bevor das Programm beendet wird?

shedepe
23.10.2016, 15:50
Ein Pointer ist auf einem 64bit System in der Regel ein uin64_t. Es sei dann man hat andere Compileroptionen angegeben.

HaWe
23.10.2016, 15:58
auch klar, aber ich will doch nicht eine Pointeradresse im RAM ausgedruckt bekommen, sondern den WERT, der in *retval steht.

Also wie kann ich den Wert ausdrucken, dass printf mir aussagekräftige Informationen liefert?
D.h., wie lautet also praktisch ein funktionierender Ausgabebefehl in der Art

printf("retval1 = %..." , *retval1); // richtiger Datentyp-Bezeichner hinter % fehlt noch!

:?:

botty
23.10.2016, 19:19
Probiers mal damit.



#include <stdio.h>
#include <pthread.h>
#include <assert.h>

int bla;

void *func(void *arg) {
printf("Thread started\nBye\n");

bla = 0;

while(1)
pthread_testcancel();

return &bla;
}

int main(void) {
pthread_t id;

void *ret;

assert(sizeof(void *) == sizeof(int));

pthread_create(&id, 0, func, 0);

printf("PTHREAD_CANCELED=%d\n", (int)PTHREAD_CANCELED);

printf("Cancel\n");
pthread_cancel(id);

printf("Join\n");
pthread_join(id, &ret);

printf("%p\t%d\n", ret, ret == PTHREAD_CANCELED);

printf("%d\n", (int)ret);

return 0;
}


Gruß botty

Edit: assert() ergänzt.

HaWe
23.10.2016, 20:05
danke, das ist jetzt interessant.

erstmal dein O-Ton:



// thread canceled

#include <stdio.h>
#include <pthread.h>
#include <assert.h>

int bla;

void *func(void *arg) {
printf("Thread started\nBye\n");

bla = 0;

while(1)
pthread_testcancel();

return &bla;
}


int main(void) {
pthread_t id;

void *ret;

assert(sizeof(void *) == sizeof(int));

pthread_create(&id, 0, func, 0);

printf("PTHREAD_CANCELED=%d\n", (int)PTHREAD_CANCELED);

printf("Cancel\n");
pthread_cancel(id);

printf("Join\n");
pthread_join(id, &ret);

printf("%p\t%d\n", ret, ret == PTHREAD_CANCELED);

printf("%d\n", (int)ret);

}



ergibt



PTHREAD_CANCELED=-1
Cancel
Thread started
Bye
Join
0xffffffff 1
-1


------------------
(program exited with code: 0)
Press return to continue



eine kleine Änderung, wenn sich der Thread selbst beendet:



// thread self-terminated

#include <stdio.h>
#include <pthread.h>
#include <assert.h>

int bla;

void *func(void *arg) {
printf("Thread started\nBye\n");

bla = 0;

//while(1)
pthread_testcancel();

return &bla;
}

int main(void) {
pthread_t id;

void *ret;

assert(sizeof(void *) == sizeof(int));

pthread_create(&id, 0, func, 0);

printf("PTHREAD_CANCELED=%d\n", (int)PTHREAD_CANCELED);

printf("auto terminated\n");
//pthread_cancel(id);

printf("Join\n");
pthread_join(id, &ret);

printf("%p\t%d\n", ret, ret == PTHREAD_CANCELED);

printf("%d\n", (int)ret);

return 0;
}






PTHREAD_CANCELED=-1
auto terminated
Join
Thread started
Bye
0x20b18 0
133912


------------------
(program exited with code: 0)
Press return to continue



erstmal fiel mir auf, dass du den Rückgabewert nur mit * und nicht mit ** deklariert hast, wie es in der pthread Deklaration stand und wie ich es dann übernommen habe.

Dann fällt auf, dass PTHREAD_CANCELED als default immer -1 zu haben scheint (als int gecastet).
EDIT:
Weiterhin erhält der return-Wert seinen Wert -1, wenn tatsächlich gecancelt wurde.

Dann ist seltsam, dass bei Selbst-Terminierung im 2. Beispiel zwar per bla eine Null zurückgegeben worden sein müsste bzw. zu sein scheint,

A-Bär:
EDIT:
Frage 1) der return-Wert jetzt nicht Null ist (wie ich erwartet hätte), sondern 133912
-wieso?

Und dann noch eine Frage zum Schluss:
Frage 2) was macht assert, denn ich habe es vorher ohne assert laufen lassen, da gab es exakt dieselben Screen-Ausgaben?

botty
23.10.2016, 20:35
Sorry, da ist mir ein Fehler unterlaufen:
Die Variable "bla" ist Blödsinn es muss in "func()" lauten:


#include <stdio.h>
#include <pthread.h>
#include <assert.h>

void *func(void *arg) {
printf("Thread started\nBye\n");

//while(1)
pthread_testcancel();

return (void*)0;
}
letztlich benutzen wir in diesem Fall den Zeiger als Integer.

Das ** in der Deklaration von pthread_join ist notwendig, da in die Variable ret in main geschrieben werden soll. Da wir in C nur Call-By-Value haben müssen wir einen Zeiger auf die Variable ret hineingeben, über den dann diese verändert wird. In Pascal würde man einen VAR Parameter verwenden (Call-By-Reference), dass gibt's aber nicht in C.

PTHREAD_CANCELED ist ein Makro was auf meiner Linuxbuchse zu "((void*)-1) expandiert. Würde man den Cast nicht im Makro haben, würde der Compiler ständig meckern. Kannst ja mal beim obigen return den cast wegnehmen, dann siehst du es.

assert ist ein Mechanismus, mit dem du in Programmen Bedingungen fomulieren kannst, die wahr sein müssen. Wenn sie zu falsch ausgewertet werden, dann bricht das Programm an der Stelle mit einer Fehlermeldung ab. Da ich lustig von "void *" auf "int" caste, wollte ich sicher sein, dass auf Deinem System diese Bedingung wahr ist (ich habe hier nur eine normale 32-Bit Linuxbüchse in einer Virtualbox laufen). Gegentest wäre im assert statt "int" mal "double" einzusetzen, dann siehst du was passiert.

Gruss botty

HaWe
23.10.2016, 21:02
ganz große KLasse, danke, so funktioniert es!
0 bei Selbstterminierung,
-1 bei pthread_cancel

und bei besonderen Ereignissen/Events kann ich eine eigene RETVAL definieren und per
return (void*)RETVAL;
jetzt auch übermitteln und nach pthread_join() dann auswerten.

Ganz toll,
vielen lieben Dank!
8)