Thread-Probleme

Nachricht
Autor
WilliWupp

Thread-Probleme

#1 Beitrag von WilliWupp » 28. Feb 2008 12:56

Hi,

ich habe hier eigentlich ein recht simples stück Code um Threads zu erzeugen:

Code: Alles auswählen

bool startThread(void* func(void*),void *arg,FILE *logHandle)
{
pthread_attr_t attr;
pthread_t      thread;
int            r;

pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
r = pthread_create(&thread,&attr,func,arg);
pthread_attr_destroy(&attr);
if (r)
   {
   showLog(logHandle,"Error: could not create thread (%d)",r);
   return false;
   }
return true;
}
Alternativ dazu sogar noch einfacher:

Code: Alles auswählen

bool startThread(void* func(void*),void *arg,FILE *logHandle)
{
pthread_t      thread;
int            r;

r = pthread_create(&thread,NULL,func,arg);
if (r)
   {
   showLog(logHandle,"Error: could not create thread (%d)",r);
   return false;
   }
return true;
}
Bei beiden habe ic hdas Problem dass ich nur ca. 150 Threads starten kann, dann schlägt das pthread_create() mit einem Fehler 12 fehl. Speicher ist genug da, the Posix-Max-Threads sind unlimited und in /proc ist die Anzahl der Threads auch nicht begrenzt. Was kann also die Ursache dafür sein?

Benutzeravatar
Janka
Beiträge: 3581
Registriert: 11. Feb 2006 19:10

#2 Beitrag von Janka » 28. Feb 2008 13:13

Die Variable "thread", in der die notwendigen Datenstrukturen zur Verwaltung des Threads aufbewahrt werden, liegt bei dir im Stackframe der Funktion startThread(). Damit ist sie nach Ende der Funktion ungültig, obwohl der Thread noch läuft. Das ist MURKS, und ein SIGSEGV ist nur eine Frage der Zeit.

Du musst mit malloc() Speicher auf dem Heap für die Datenstruktur reservieren, auch wenn du sie in deinem Programm selbst nicht benötigst.

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

WilliWupp

#3 Beitrag von WilliWupp » 28. Feb 2008 13:35

Aaah...das klingt einleuchtend.

Nur: wie/wann gebe ich den Datenbereich wieder frei? Der Thread weiß davon ja nix...

WilliWupp

#4 Beitrag von WilliWupp » 28. Feb 2008 13:40

Nachtrag: Jetzt sieht der Code so aus - und trotzdem reicht es nur für knapp 300 Threads:

Code: Alles auswählen

bool startThread(void* func(void*),void *arg,FILE *logHandle)
{
pthread_attr_t attr;
pthread_t     *thread;
int            r;

errno=0;
thread=(pthread_t*)malloc(sizeof(pthread_t));
if (!thread)
   {
   showLog(logHandle,"Error: could not allocate thread (%d)",errno);
   return false;
   }
r=pthread_attr_init(&attr);
r=pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
r = pthread_create(thread,&attr,func,arg);
pthread_attr_destroy(&attr);
if (r)
   {
   showLog(logHandle,"Error: could not create thread (%d)",r);
   return false;
   }
return true;
}

Benutzeravatar
Janka
Beiträge: 3581
Registriert: 11. Feb 2006 19:10

#5 Beitrag von Janka » 28. Feb 2008 15:57

Mit pthread_attr_t attr ist es genau dasselbe!

Diese Struktur ist allerdings konstant und kann auch für mehrere Threads benutzt werden, wenn alle diese Threads dieselben Attribute haben sollen. Du kannst hier also z.B. eine globale Konstante benutzen.

Wenn du in deinem Programm dynamisch Threads erzeugst, musst du dir die Pointer auf die Thread-Datenobjekte irgendwo merken und nach dem Ende des Threads wieder freigeben. Im allgemeinen bastelt man das so, dass der Worker-Thread kurz vor seinem Ende dem Main-Thread signalisiert, dass er sich beenden möchte. Der main-Thread geht dann in ein pthread_join() und wartet auf die korrekte Beendigung des Worker-Threads. Dieser macht dann pthread_exit() und der Main-Thread kommt auf dem pthread_join() mit dem Exitcode des Worker-Threads zurück. Danach kann er dann den Speicher für die Thread-Datenstruktur des jetzt beendeten Worker-Threads freigeben.

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

WilliWupp

#6 Beitrag von WilliWupp » 28. Feb 2008 17:35

Aaaaalso, pthread_attr_t ist jetzt static da es mit den immer wieder gleichen Parametern verwendet wird.

Trotzdem: mehr als knapp 300 Threads gehen nicht, dann ist Schluss. Woran könnte das noch liegen?

Benutzeravatar
Janka
Beiträge: 3581
Registriert: 11. Feb 2006 19:10

#7 Beitrag von Janka » 29. Feb 2008 12:34

Du solltest dir nochmal ein Grundlagenbuch zu C (nicht C++!) schnappen.

Die Speicherklasse "static" bewirkt mitnichten, dass eine Variable "immer mit gleichen Parametern verwendet wird" -- kein Ahnung, was das heißen soll. "static" bedeutet, dass das Symbol außerhalb des Blockes, in dem es definiert wird, unsichtbar ist. Das ist bei funktionslokalen Variablen zwangsläufig der Fall, weil diese mit dem Stackframe der Funktion nach deren Ende ja wieder ungültig werden.

Sinnvoll ist "static" dagegen außerhalb von Blöcken. Ein als "static" markiertes Symbol ist außerhalb des Quellcodefiles unsichtbar.

Zu deinem Problem -- folgender Code funzt:

Code: Alles auswählen


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

pthread_attr_t attr; 

void *thread_function&#40;void *arg&#41;
&#123;
	/* Sleep 1000s */
	sleep&#40;1000&#41;;

	/* This point should never be reached before the process is terminated...*/
	fprintf&#40;stderr,"Reached a point that should never be reached!"&#41;;
&#125;

void startThread&#40;void* func&#40;void*&#41;, void *arg&#41; 
&#123; 
	pthread_t     *thread; 
	int            r; 
 
	if &#40;&#40;thread=&#40;pthread_t*&#41;malloc&#40;sizeof&#40;pthread_t&#41;&#41;&#41;==NULL&#41; &#123; 
		perror&#40;"startThread, malloc"&#41;;
		exit&#40;EXIT_FAILURE&#41;;
	&#125; 
	if &#40;pthread_create&#40;thread,&attr,func,arg&#41;==-1&#41; &#123; 
		perror&#40;"startThread, pthread_create"&#41;;
		exit&#40;EXIT_FAILURE&#41;;
	&#125; 
&#125;


int main&#40;int argc, char *argv&#91;&#93;&#41;
&#123;
	int i;

	if &#40;pthread_attr_init&#40;&attr&#41;==-1&#41; &#123; 
		perror&#40;"main, pthread_attr_init"&#41;;
		exit&#40;EXIT_FAILURE&#41;;
	&#125;
	if &#40;pthread_attr_setdetachstate&#40;&attr, PTHREAD_CREATE_DETACHED&#41;==-1&#41; &#123;
		perror&#40;"main, pthread_attr_setdetachstate"&#41;;
		exit&#40;EXIT_FAILURE&#41;; 
	&#125; 
 
	for&#40;i=0;i<1000;i++&#41; &#123;
		startThread&#40;thread_function,NULL&#41;;
		fprintf&#40;stdout,"%d\n",i&#41;;
	&#125;

	/* Wait 500s to let the user inspect the example. */
	sleep&#40;500&#41;;

	if &#40;pthread_attr_destroy&#40;&attr&#41;==-1&#41; &#123;
		perror&#40;"main, pthread_attr_destroy"&#41;;
		exit&#40;EXIT_FAILURE&#41;;
	&#125; 
&#125;
Und das Makefile dazu:

Code: Alles auswählen

.PHONY&#58; all clean

all&#58; thread
clean&#58;
	-rm thread *.o

thread&#58; thread.o
	$&#40;CC&#41; -lpthread -o $@ $^
Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

Gast

#8 Beitrag von Gast » 02. Mär 2008 14:51

Janka hat geschrieben:"static" bedeutet, dass das Symbol außerhalb des Blockes, in dem es definiert wird, unsichtbar ist.
Das ist bei Variablen die innerhalb einer Funktion als static definiert werden nur die halbe Wahrheit: diese bleiben zusätzlich auch nach verlassen der Funktion erhalten, sprich man kann auf deren Inhalt später wieder zugreifen. Und noch viel besser: ein

static long i=-1

bewirkt, dass i exakt einmal mit -1 initialisiert wird und nicht etwa bei jedem Aufruf dieser Funktion erneut.

Benutzeravatar
Janka
Beiträge: 3581
Registriert: 11. Feb 2006 19:10

#9 Beitrag von Janka » 02. Mär 2008 20:18

Tatsächlich, du hast recht. Wieder was gelernt! Man kann also auf diese Weise von innerhalb eines Blockes aus globale Variablen definieren, die dann nur innerhalb des Blockes sichtbar sind.

Testcode:

Code: Alles auswählen

#include <stdio.h>

#define STATIC static

void f1&#40;int s&#41;
&#123;
  STATIC int a;
  STATIC int b;

  if &#40;s&#41; &#123;
    a=12;
    b=34;
  &#125;

  printf&#40;"f1&#58; a&#58; %d, b&#58; %d\n",a,b&#41;;
&#125;

void f2&#40;int s&#41;
&#123;
  STATIC int a;
  STATIC int b;

  if &#40;s&#41; &#123;
    a=56;
    b=78;
  &#125;

  printf&#40;"f2&#58; a&#58; %d, b&#58; %d\n",a,b&#41;;
&#125;


int main&#40;void&#41;
&#123;
  f1&#40;1&#41;;
  f2&#40;1&#41;;
  f1&#40;0&#41;;
  f2&#40;0&#41;;
&#125;
Den Unterschied sieht man, wenn man #define STATIC static durch #define STATIC ersetzt.

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

WilliWupp

#10 Beitrag von WilliWupp » 03. Mär 2008 13:24

So, egal wie ich es mache, ob "static" in- oder außerhalb der Funktion, ich komme trotzdem nicht über 296 Threads hinaus...

Benutzeravatar
Janka
Beiträge: 3581
Registriert: 11. Feb 2006 19:10

#11 Beitrag von Janka » 03. Mär 2008 22:18

Hast du wenigstens mal mein Beispiel ausprobiert? Das klappt hier nämlich. Wenn es bei dir auch klappt, bau' es schrittweise in deinen Code um und teste nach jedem Schritt. So findet man Fehler, die man zuvor aus lauter Betriebsblindheit nicht gesehen hat.

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

WilliWupp

#12 Beitrag von WilliWupp » 05. Mär 2008 11:04

Ja, der Code ist mittlerwile exakt so drin, ohne jede Veränderung...

Benutzeravatar
Janka
Beiträge: 3581
Registriert: 11. Feb 2006 19:10

#13 Beitrag von Janka » 05. Mär 2008 16:42

Dann hast du vermutlich an einer anderen Stelle ein Problem.

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

WilliWupp

#14 Beitrag von WilliWupp » 06. Mär 2008 7:49

Naja, ich habe eher bei dir ein Problem entdeckt:

Code: Alles auswählen

if &#40;pthread_create&#40;thread,&attr,func,arg&#41;==-1&#41;
Du überprüfst nur auf den Rückgabewert -1 als Fehlerfall, allerdings ist jeder nitch-0-Wert ein Fehler:
If successful, the pthread_create() function shall return zero; otherwise, an error number shall be returned to indicate the error.
Und bei mir gibt es ja auch keine -1 als Fehler sondern eine 12, was dein Beispiel gar nicht bemerkt.

Benutzeravatar
Janka
Beiträge: 3581
Registriert: 11. Feb 2006 19:10

#15 Beitrag von Janka » 07. Mär 2008 14:06

Stimmt, du hast recht. Die pthtread-Funktionen setzen nicht errno, sondern liefern die Fehlernummer direkt zurück. Vermutlich, um "errno" der "normalen" Funktionen nicht zu überschreiben.

Ich erhalte mit einer korrigierten Version meines Programms ebenfalls 12 (ENOMEM) zurück, ab Thread Nummer 381.

Janka
Ich vertonne Spam immer in /dev/dsp statt /dev/null.
Ich mag die Schreie.

Antworten