Kleines einfaches Beispiel zu multi-threading mit PThreads in C und mit Boost in C++

Thomas Darimont

Erfahrenes Mitglied
Hallo,

hier mal ein kleines Beispiel zu multi-threading mit PThreads in C als DevCPP Projekt.
Als PThread Implementierung habe ich PThread-win32 verwendet:
http://sourceware.org/pthreads-win32/

main.c
C:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

/*
 * function do_something wird mit der entsprechenden Thread ID als Parameter von den erzeugten Threads aufgerufen
 */
void *do_something(void *arg){
     int *args = ((int*)arg);

     printf("hallo welt from thread %d!\n", args[0]);

      int i;      
      for(i = 0; i < 100; i++){
              printf("Thread(%d) sleep: %d  num: %d\n" ,args[0],args[1], i ); 
              sleep(args[1]);
      }
     return NULL;
}

int main(int argc, char *argv[])
{
  
  int status;
  pthread_t thread1;
  int thread1_args[2];
  thread1_args[0]=1; //Thread ID des ersten Threads
  thread1_args[1]=250; //sleep in Millisekunden innerhalb jeder Schleifeniteration in do_something(..)
  
  //Thread 1 starten
  status = pthread_create(&thread1, NULL, do_something, &thread1_args);
  if(status != 0){
    printf("Error creating thread1!\n");          
  }else{
    printf("thread1 started\n");          
  }        
  
  pthread_t thread2;
  int thread2_args[2];
  thread2_args[0]=2; //Thread ID des zweiten Threads
  thread2_args[1]=100; //sleep in Millisekunden innerhalb jeder Schleifeniteration in do_something(..)
  
  //Thread 2 starten
  status = pthread_create(&thread2, NULL, do_something, &thread2_args);
  if(status != 0){
    printf("Error creating thread2!\n");          
  }else{
    printf("thread2 started\n");          
  }        
  
  //Auf Beendigung von Thread 1 warten
  status = pthread_join(thread1,NULL);
  if(status != 0){
    printf("Error joining thread1!\n");          
  }else{
    printf("thread1 joined\n");          
  }          

  //Auf Beendigung von Thread 2 warten
  status = pthread_join(thread2,NULL);
  if(status != 0){
    printf("Error joining thread2!\n");          
  }else{
    printf("thread2 joined\n");          
  }          
  
  system("PAUSE");	
  return 0;
}

Ausgabe:
Code:
thread1 started
thread2 started
hallo welt from thread 1!
Thread(1) sleep: 250  num: 0
hallo welt from thread 2!
Thread(2) sleep: 100  num: 0
Thread(2) sleep: 100  num: 1
Thread(2) sleep: 100  num: 2
Thread(1) sleep: 250  num: 1
Thread(2) sleep: 100  num: 3
Thread(2) sleep: 100  num: 4
Thread(1) sleep: 250  num: 2
Thread(2) sleep: 100  num: 5
Thread(2) sleep: 100  num: 6
Thread(2) sleep: 100  num: 7
Thread(1) sleep: 250  num: 3
....

Gruß Tom
 

Anhänge

  • PThread_Example.zip
    8 KB · Aufrufe: 55
Eine ID zu vergeben ist nicht notwendig. Dafür gibts pthread_self().

Desweiteren kann man den Rückgabe-Wert von pthread_create() auch mittels strerror() etwas deutlicher ausgeben.

Das Joinen eines nicht existierenden Threads ist nicht notwendig und liefert immer den Fehler ESRCH. Auch hier ist es sinnvoll, den Fehler-Code an strerror() zu übergeben.

Ansonsten finde ich deinen Sourcecode schlecht formatiert. (Sorry!)

Was bei Pthreads für Win32 noch zu erwähnen wäre: Es wäre anzuraten die Methoden pthread_win32_process_attach_np() bzw. pthread_win32_process_detach() zu verwenden, wenn man PThreads innerhalb einer DLL verwenden will. Hier ist die Erklärung dafür: http://sourceware.org/pthreads-win32/manual/pthread_win32_attach_detach_np.html.
 
Hallo saftmeister,

vielen Dank für den Tipp mit pthread_self(), ich habe das Beispiel entsprechend angepasst.

Das Joinen eines nicht existierenden Threads ist nicht notwendig und liefert immer den Fehler ESRCH. Auch hier ist es sinnvoll, den Fehler-Code an strerror() zu übergeben.
Ja das steht so in der Spec von PThreads. In meinem Beispiel wollte ich aber beispielhaft zeigen wie man auf Threads warten kann. Deshalb habe ich die Threads entsprechend "lange" laufen lassen ... um auch darauf warten zu können, dass sie mit ihrer "Arbeit" fertig werden.

Ansonsten finde ich deinen Sourcecode schlecht formatiert. (Sorry!)
Jo das stimmt wohl :).

Wie oben Beschrieben habe ich das Beispiel mit Bloodsheet DevCPP eingetippt - mehr schafft mein Lenevo IDEA Pad auf dem ich das geschrieben habe (und das auch noch während einer Autofahrt, natürlich als Beifahrer ;-)) nicht. Der Editor hat leider keine Source Format Möglichkeiten...

Ich habe das Beispiel nun nochmal mit Eclipse CDT Formatiert / und deinen Vorschlägen angepasst.

Insgesamt ist das Beispiel nur ein kleines Nebenprodukt das bei dem Durcharbeiten der Übungen zu dem Buch entstanden ist:
http://www.amazon.de/Multicore-Soft...=sr_1_1?s=books&ie=UTF8&qid=1331326734&sr=1-1

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

void *do_something(void *arg) {
	int *args = (int*) arg;
	int sleepDuration = args[0];
	int threadId = (int) pthread_self().p;

	printf("Hello World from thread %d %d!\n", threadId, sleepDuration);

	int i;
	for (i = 0; i < 10; i++) {
		printf("Thread(%d) sleep: %d counter: %d\n", threadId, sleepDuration, i);
		sleep(sleepDuration);
	}
	return NULL;
}

int main(int argc, char *argv[]) {

	pthread_t thread1;
	int thread1_args[1];
	thread1_args[0] = 250;

	//Start Thread 1
	int status = pthread_create(&thread1, NULL, do_something, &thread1_args);
	if (status != 0) {
		printf("Error creating thread1!\n");
	} else {
		printf("thread1 started\n");
	}

	pthread_t thread2;
	int thread2_args[1];
	thread2_args[0] = 100;

	//Start Thread 2
	status = pthread_create(&thread2, NULL, do_something, &thread2_args);
	if (status != 0) {
		printf("Error creating thread2!\n");
	} else {
		printf("thread2 started\n");
	}

	//Wait for Threads to finish

	//Wait for Thread 1 to finish
	status = pthread_join(thread1, NULL);
	if (status != 0) {
		printf("Error joining thread1! Error Code: %d\n");
	} else {
		printf("thread1 joined\n");
	}

	//Wait for Thread 2 to finish
	status = pthread_join(thread2, NULL);
	if (status != 0) {
		printf("Error joining thread2! Error Code: %d\n", status);
	} else {
		printf("thread2 joined\n");
	}

	system("PAUSE");
	return 0;
}

Gruß Tom
 
Hallo,

ich hab dein Beispiel mal zum Anlass genommen, mir Boost.Thread genauer anzuschauen. Folgendes ist dabei herausgekommen:

C++:
#include <cstdlib>
#include <iostream>

#include <boost/thread.hpp>

void do_something(int sleepDuration) {
  boost::thread::id threadId = boost::this_thread::get_id();

  std::cout << "Hello world from thread " << threadId << "!" << std::endl;

  for (int i = 0; i < 10; ++i) {
    std::cout << "Thread (" << threadId << ") "
              << "sleep: " << sleepDuration << " "
              << "counter: " << i
              << std::endl;
    boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDuration));
  }
}

int main() {
  try {
    boost::thread thread1(do_something, 250);
    boost::thread thread2(do_something, 100);

    thread1.join();
    thread2.join();
  } catch (boost::exception& e) {
    std::cerr << boost::diagnostic_information(e) << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

Grüße,
Matthias
 
Hallo,

sehr schön! Danke Matthias! Jetzt haben wir auch noch ein ordentliches Beispiel mit C++ / Boost. :)
Boost ist IMHO eine der elegantesten C++ Bibliotheken die es gibt!

Gruß Tom
 
Hallo,

... da wir gerade dabei sind, habe ich auch mal ein wenig mit Boost herumgespielt und das obige Beispiel um das Starten von beliebig vielen Threads mit Beispielen zu verschiedenen Synchronisationsvarianten (Barrier, Mutex). erweitert.

Ich habe noch nicht so viel Erfahrung mit CPP / Boost ... deshalb ist das Beispiel mit Vorsicht zu genießen :)

Erstellt mit Eclipse CDT, Boost Version 1.49.0 und Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86

main.cpp
C++:
#include <cstdlib>
#include <iostream>

#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/random.hpp>

void do_something(boost::barrier* start_sync_barrier, boost::mutex* output_mutex, int iterations, int sleepDuration) {
	boost::thread::id threadId = boost::this_thread::get_id();



	/*
	 * Synchronisation der Standardausgabe
	 */
	output_mutex->lock();
	std::cout << "Hello world from thread " << threadId << "!" << std::endl;
	output_mutex->unlock();

	start_sync_barrier->wait();

	for (int i = 0; i < iterations; ++i) {
		output_mutex->lock();
		std::cout << "Thread (" << threadId << ") " << "sleep: " << sleepDuration << " " << "counter: " << i << std::endl;
		output_mutex->unlock();
		boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDuration));
	}
}

int main() {
	try {

		int iterations = 25;
		int numberOfThreads = 5;

		/*
		 * wir warten mit einer Barriere darauf, dass alle Threads gestartet wurden. +1 da wir im Main-Thread warten
		 */
		boost::barrier* start_sync_barrier = new boost::barrier(numberOfThreads + 1);

		/*
		 * Um die Ausgabe auf der Konsole nicht durcheinander zu bringen verwenden wir einen Mutex zur synchronisation des Ausgabestroms
		 */
		boost::mutex* output_mutex = new boost::mutex;

		/**
		 * Wir generieren eine ganzzahlige Zufallszahl im Bereich von 100 bis 250
		 */
		boost::random::mt19937 rng;
		boost::random::uniform_int_distribution<> sleepTimesDistribution(100,250);

		/*
		 * Wir erzeugen eine bestimmte Anzahl (numberOfThreads) von Threads Instanzen im Heap und speichern Pointer auf diese Instanzen in einer Liste.
		 */
		std::list<boost::thread*> pool;
		for (int i = 0; i < numberOfThreads; i++) {
			boost::thread *thread = new boost::thread(do_something, start_sync_barrier, output_mutex, iterations, (int) sleepTimesDistribution(rng));
			pool.push_back(thread);
		}

		/*
		 * Wir warten darauf, dass alle Threads gestartet wurden und ihre Begrüssungsmeldung ausgegeben haben.
		 */
		start_sync_barrier-> wait();
		delete start_sync_barrier;

		/*
		 *Wir warten mit einem join() darauf, dass alle Threads mit ihrer Arbeit fertig sind
		 */
		while (!pool.empty()) {
			boost::thread* thread = pool.front();
			thread->join();
			pool.pop_front();
			delete thread;
		}

		delete output_mutex;
	} catch (boost::exception& e) {
		std::cerr << boost::diagnostic_information(e) << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

Ausgabe:
Code:
Hello world from thread 004F3218!
Hello world from thread 004F3278!
Hello world from thread 004F3630!
Hello world from thread 004F4178!
Hello world from thread 004F4740!
Thread (004F4740) sleep: 119 counter: 0
Thread (004F3218) sleep: 223 counter: 0
Thread (004F3630) sleep: 236 counter: 0
Thread (004F4178) sleep: 226 counter: 0
Thread (004F3278) sleep: 120 counter: 0
Thread (004F3278) sleep: 120 counter: 1
Thread (004F4740) sleep: 119 counter: 1
Thread (004F3218) sleep: 223 counter: 1
Thread (004F4178) sleep: 226 counter: 1
Thread (004F3630) sleep: 236 counter: 1
Thread (004F4740) sleep: 119 counter: 2
Thread (004F3278) sleep: 120 counter: 2
Thread (004F4740) sleep: 119 counter: 3
Thread (004F3278) sleep: 120 counter: 3
Thread (004F3218) sleep: 223 counter: 2
Thread (004F4178) sleep: 226 counter: 2
Thread (004F3630) sleep: 236 counter: 2
Thread (004F4740) sleep: 119 counter: 4
Thread (004F3278) sleep: 120 counter: 4
...

Gruß Tom
 
Ich habe noch nicht so viel Erfahrung mit CPP / Boost ... deshalb ist das Beispiel mit Vorsicht zu genießen :)
Der Code ist nicht ganz optimal. Ich üb mich mal in konstruktiver Kritik:

Rohe Zeiger sollte man in C++ generell nicht verwenden. Stell dir folgendes vor:
C++:
try {
  int* i = new int;
  f(i);
  delete pi;
} catch (...) {
  // Memory leak!
}
Wenn f eine Ausnahme wirft, hat man ein Memory Leak produziert, da das delete nie ausgeführt wird. Daher sofern möglich Variablen automatisch verwalten lassen (auf dem Stack anlegen). Zeiger, sofern benötigt, in einen passenden Smart Pointer stecken (unique_ptr, shared_ptr…), der ein Abräumen des Zeigers auch bei Exceptions garantiert.

Ähnliches gilt für die Mutex. Fliegt zwischen lock() und unlock() eine Ausnahme, ergibt das wahrscheinlich einen Deadlock. Daher besser eine der Locks verwenden.

C-Style-Casts wie (int)foo sind in C++ verpönt, da sie fast alles in alles casten können. Man sollte besser die speziellen Cast-Operatoren static_cast (für 90% der Fälle), dynamic_cast (polymorphischer Cast, möglicherweise mit Runtime-Check), reinterpret_cast (nahezu beliebiges Umwandeln von Zeigertypen, mit Vorsicht zu genießen), const_cast (kann const entfernen, nur in absoluten Notfällen zu verwenden) verwenden. Praktisch ist auch boost::numeric_cast, welcher bei Nicht-Darstellbarkeit im Zieltypen eine Exception wirft. Abgesehen davon ist der Cast auf int in deinem Code sowieso überflüssig, da ja schon ein int vorliegt :)

Die Modifikation der Thread-Liste (mittels pop_front()) ist nicht nötig. Iterieren geht über Iteratoren (wer hätte das gedacht ;)):
C++:
std::list<boost::thread>::iterator it;
for (it = pool.begin(); it != pool.end(); ++it) {
  it->join();
}
Oder für Fortgeschrittene mit einem Standard-Algorithmus:
C++:
std::for_each(pool.begin(), pool.end(), std::mem_fn(&boost::thread::join));

Ansonsten sollte man sich für ein Namensschema entscheiden und nicht ein_schema und einAnderesSchema vermischen ;)

Hier mal eine verbesserte Version:
C++:
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <list>
 
#include <boost/thread/thread.hpp>
#include <boost/thread/barrier.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/random.hpp>
 
void do_something(boost::barrier& startSyncBarrier,
                  boost::mutex& outputMutex, int iterations,
                  int sleepDuration) {
    boost::thread::id threadId = boost::this_thread::get_id();
 
    /*
     * Synchronisation der Standardausgabe
     */
    boost::unique_lock<boost::mutex> lock(outputMutex);
    std::cout << "Hello world from thread " << threadId << "!" << std::endl;
    lock.unlock();
 
    startSyncBarrier.wait();
 
    for (int i = 0; i < iterations; ++i) {
        lock.lock();
        std::cout << "Thread (" << threadId << ") "
                  << "sleep: " << sleepDuration << " "
                  << "counter: " << i << std::endl;
        lock.unlock();
        boost::this_thread::sleep(boost::posix_time::milliseconds(sleepDuration));
    }
}
 
int main() {
    try {
        const int iterations = 25;
        const int numberOfThreads = 5;
 
        /*
         * wir warten mit einer Barriere darauf, dass alle Threads gestartet wurden. +1 da wir im Main-Thread warten
         */
        boost::barrier startSyncBarrier(numberOfThreads + 1);
 
        /*
         * Um die Ausgabe auf der Konsole nicht durcheinander zu bringen verwenden wir einen Mutex zur synchronisation des Ausgabestroms
         */
        boost::mutex outputMutex;
 
        /**
         * Wir generieren eine ganzzahlige Zufallszahl im Bereich von 100 bis 250
         */
        boost::random::mt19937 rng;
        boost::random::uniform_int_distribution<> sleepTimesDistribution(100, 250);
 
        /*
         * Wir erzeugen eine bestimmte Anzahl (numberOfThreads) von Threads Instanzen und speichern diese in einer Liste.
         */
        std::list<boost::thread> pool;
        for (int i = 0; i < numberOfThreads; i++) {
            pool.push_back(boost::thread(
                do_something,
                std::ref(startSyncBarrier),
                std::ref(outputMutex),
                iterations,
                sleepTimesDistribution(rng)));
        }
 
        /*
         * Wir warten darauf, dass alle Threads gestartet wurden und ihre Begrüssungsmeldung ausgegeben haben.
         */
        startSyncBarrier.wait();
 
        /*
         * Wir warten mit einem join() darauf, dass alle Threads mit ihrer Arbeit fertig sind
         */
        std::for_each(
            pool.begin(), pool.end(), std::mem_fn(&boost::thread::join));
    } catch (boost::exception& e) {
        std::cerr << boost::diagnostic_information(e) << std::endl;
        return EXIT_FAILURE;
    }
 
    return EXIT_SUCCESS;
}

Grüße,
Matthias
 
Hallo Matthias,

danke für die Korrekturen, deine Variante gefällt mir auch viel besser :)

Die Modifikation der Thread-Liste (mittels pop_front()) ist nicht nötig. Iterieren geht über Iteratoren (wer hätte das gedacht ):
Code cpp:
Das war mein laienhafter Versuch einmal "gründlich" aufzuräumen :D

Das Konzept mit den Scoped Resources / Pointern die "automatisch" nach dem Verlassen des Scopes ihre gewrappedten Resourcen wgräumen ist wirklich ne große Erleichterung! :)

Ansonsten sollte man sich für ein Namensschema entscheiden und nicht ein_schema und einAnderesSchema vermischen
huch... in Java wär mir das nicht passiert *schwör* schame on me :)

Schön, das wir so viele Aspekte im Rahmen des kleinen Beispiels unterbringen konnten :D

Gruß Tom
 
Hallo,

ich kann dein Beispiel leider nicht kompilieren, weder mit g++ (MinGW) noch mit CL.

Beispielsweise kennt der Compiler, std::ref und std::mem_fn nicht. Fehlen mir da noch weitere Header Dateien oder habe ich zu alte Header Dateien?

Mit boost::ref bzw. boost::mem_fn kann komme ich dann schon weiter... allerdings schlägt auch damit das Kompilieren fehl:
Code:
...

**** Build of configuration Debug for project BoostThreadingExample3 ****

**** Internal Builder is used for build               ****
g++ -IC:\development\cpp\libs\boost_1_49_0 -O0 -g3 -Wall -c -fmessage-length=0 -o main.o ..\main.cpp
In file included from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/mingw32/bits/c++allocator.h:34:0,
                 from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/bits/allocator.h:48,
                 from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/string:43,
                 from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/bits/locale_classes.h:42,
                 from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/bits/ios_base.h:43,
                 from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/ios:43,
                 from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/ostream:40,
                 from c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/iostream:40,
                 from ..\main.cpp:4:
c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/ext/new_allocator.h: In member function 'void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = boost::thread, __gnu_cxx::new_allocator<_Tp>::pointer = boost::thread*]':
c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/bits/stl_list.h:476:6:   instantiated from 'std::list<_Tp, _Alloc>::_Node* std::list<_Tp, _Alloc>::_M_create_node(const value_type&) [with _Tp = boost::thread, _Alloc = std::allocator<boost::thread>, std::list<_Tp, _Alloc>::_Node = std::_List_node<boost::thread>, std::list<_Tp, _Alloc>::value_type = boost::thread]'
c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/bits/stl_list.h:1515:42:   instantiated from 'void std::list<_Tp, _Alloc>::_M_insert(std::list<_Tp, _Alloc>::iterator, const value_type&) [with _Tp = boost::thread, _Alloc = std::allocator<boost::thread>, std::list<_Tp, _Alloc>::iterator = std::_List_iterator<boost::thread>, std::list<_Tp, _Alloc>::value_type = boost::thread]'
c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/bits/stl_list.h:988:9:   instantiated from 'void std::list<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = boost::thread, _Alloc = std::allocator<boost::thread>, std::list<_Tp, _Alloc>::value_type = boost::thread]'
..\main.cpp:70:45:   instantiated from here
c:\development\cpp\mingw\bin\../lib/gcc/mingw32/4.6.1/include/c++/ext/new_allocator.h:108:9: error: passing 'const boost::thread' as 'this' argument of 'boost::thread::operator boost::detail::thread_move_t<boost::thread>()' discards qualifiers [-fpermissive]
Build error occurred, build is stopped
Time consumed: 1642  ms.  

...

Ich könnte jetzt nun das Compiler-Flag -fpermissive angeben... das hat aber nach kurzer Recherche
(http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html) noch andere Effekte...

Code:
            pool.push_back(boost::thread(
                do_something,

                boost::ref(startSyncBarrier),
                boost::ref(outputMutex),
                iterations,
                sleepTimesDistribution(rng))); //Zeile 70

Ich verwende Eclipse CDT mit MinGW 4.6.1 und Boost 1.49_0 (unter Windows 7 64-Bit) habe ich da noch irgendwas vergessen?

Gruß Tom
 
Hi.
Beispielsweise kennt der Compiler, std::ref und std::mem_fn nicht. Fehlen mir da noch weitere Header Dateien oder habe ich zu alte Header Dateien?
Beides sind Funktionen des aktuellen C++ Standards C++11.

Beim g++ mußt du da die Option "-std=c++0x" angeben.

Statt mem_fn kannst du auch (die alte) std::mem_fun_ref verwenden.

Aushilfsweise kann man natürlich auch boost::ref bzw. boost::mem_fn nehmen. Oder man bindet die boost TR1 Header ein, dann kann man auch std::ref und std::mem_fn verwenden.

Wobei, wenn man schon C++11 nutzt, dann kann man in dem Fall Boost natürlich auch ganz beiseite lassen.
Mit boost::ref bzw. boost::mem_fn kann komme ich dann schon weiter... allerdings schlägt auch damit das Kompilieren fehl
Das Problem ist, dass die Thread Klasse nicht kopierbar ist (da es nur eine 1:[0, 1] Beziehung zwischen einem std::thread Objekt und dem System-Thread geben darf).

Objekte, die man mit Standard-Kontainerklassen der STL verwalten will, müssen aber kopierbar sein -- es sei denn man benutzt C++11, dann können auch "bewegliche" (moveable) Typen verwaltet werden.

Zur Verwaltung der Threads könntest du auch einfach boost::thread_group verwenden und dementsprechend dessen join_all() Methode aufrufen.

Gruß
 
Zurück