Thread-Probleme

Message
Author
WilliWupp

Thread-Probleme

#1 Post by WilliWupp »

Hi,

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

Code: Select all

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: Select all

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?

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#2 Post by Janka »

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 Post by WilliWupp »

Aaah...das klingt einleuchtend.

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

WilliWupp

#4 Post by WilliWupp »

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

Code: Select all

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;
}

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#5 Post by Janka »

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 Post by WilliWupp »

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?

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#7 Post by Janka »

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: Select all


#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: Select all

.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 Post by Gast »

Janka wrote:"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.

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#9 Post by Janka »

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: Select all

#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 Post by WilliWupp »

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

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#11 Post by Janka »

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 Post by WilliWupp »

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

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#13 Post by Janka »

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 Post by WilliWupp »

Naja, ich habe eher bei dir ein Problem entdeckt:

Code: Select all

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.

User avatar
Janka
Posts: 3585
Joined: 11. Feb 2006 19:10

#15 Post by Janka »

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.

Post Reply