3 * @purpose Implentation of multi-threaded N-Body simulator using pthreads
4 * @author Sam Moore (20503628) - 2012
7 #include "nbody.h" // Declarations
8 #include "../single-thread/nbody.c" // Include all functions from the single threaded version
10 #include "graphics.h" // For declaration of Graphics_Run only
13 // --- Variable declarations --- //
15 pthread_t compute_thread; // The thread responsible for computations; it spawns worker threads (* terms and conditions apply)
17 pthread_t * worker_thread = NULL; //Array of worker threads responsible for Force and Position updates
18 System * sub_system = NULL; //Array of Systems used to divide up the main "universe" System for worker threads
19 System * nested_sub_system = NULL; //Array of Systems for division of "universe" for the nested worker threads
21 pthread_mutex_t mutex_runstate; // Mutex around the runstate
26 Barrier force_barrier; // I laughed at this variable name. A bit sad really.
27 Barrier position_barrier;
30 Barrier graphics_barrier;
32 pthread_mutex_t mutex_threads_running;
33 int threads_running = 0;
36 * @function Compute_Thread
37 * @purpose Thread - Continuously computes steps for a system of bodies. Seperate from graphics functions.
38 * Spawns worker threads to divide up computation.
39 * @param arg - Can be cast to the System for which computations are performed
42 * This will always be (void*)(&universe) where universe is the global System variable that has every Body in it.
43 * But I don't like global variables. And since the argument had to be passed, I thought I might as well use it.
44 * That way, when I change "universe" to "solar_system", I only have to change the argument where this is called, not all through it.
45 * Find and replace? Who uses that!?
49 void * Compute_Thread(void * arg)
52 System * s = (System*)(arg); //cast argument to a System*
56 // If no number of threads provided, use the default value, unless someone (me) changed that to a stupid value
57 if (options.num_threads <= 0)
58 options.num_threads = (DEFAULT_WORKING_THREADS > 1) ? DEFAULT_WORKING_THREADS : 1; // Fear the ternary operator!
60 if (options.nested_threads <= 0)
61 options.nested_threads = 1;
63 // Do a sanity check; there is no point spawning more threads than bodies.
64 if (options.num_threads > s->N)
67 "Warning: Using %u threads instead of %u specified, because there are only %u bodies to simulate!\n",
68 s->N, options.num_threads, s->N);
69 options.num_threads = s->N;
72 // Initialise the barriers ("shields up!")
73 Barrier_Init(&force_barrier, options.num_threads);
74 Barrier_Init(&position_barrier, options.num_threads);
75 Barrier_Init(&graphics_barrier, 1);
79 if (options.num_threads > 1) // If we require additional worker threads...
81 // Allocate worker threads and sub systems
83 sub_system = Split_System(&universe, options.num_threads);
85 if (options.nested_threads > 1)
86 nested_sub_system = Split_System(&universe, options.nested_threads);
88 #ifdef PERSISTENT_THREADS // Code for the smart way of doing it (spawn threads once, keep them running)
89 worker_thread = Allocate_Threads(options.num_threads-1);
90 // Spawn a bunch of worker threads, and let them all do their thing.
91 // Note the "-1". Because this thread can do work too!
92 for (unsigned i = 0; i < options.num_threads-1; ++i)
94 if (pthread_create(worker_thread+i, NULL, Worker_Thread, (void*)(sub_system+i)) != 0)
96 perror("In compute thread, couldn't create worker thread");
101 Worker_Thread((void*)(sub_system+options.num_threads-1)); // This thread becomes a worker thread
103 worker_thread = Allocate_Threads(options.num_threads);
104 #endif //PERSISTENT_THREADS
107 #ifdef PERSISTENT_THREADS
108 else // We only require one worker thread...
110 // So just do all computations in this thread
111 while (!ExitCondition())
115 // If required, wait for graphics to finish drawing
116 if (options.draw_graphics && options.pedantic_graphics)
117 Barrier_Wait(&graphics_barrier);
121 if (options.draw_graphics && options.pedantic_graphics)
122 Barrier_ForceExit(&position_barrier); //Make the graphics continue
128 #else // Code for the stupid way of doing it (respawn threads each step)
129 // (ie: The way I immediately implemented and didn't realise was stupid until someone told me)
134 // Run until we can't run anymore
135 while (!ExitCondition())
139 if (options.num_threads <= 1) // If there is only 1 worker thread...
141 // Just do everything in this thread
143 // If required, wait for graphics to finish drawing
144 if (options.draw_graphics && options.pedantic_graphics)
145 Barrier_Join(&graphics_barrier);
150 if (options.draw_graphics && options.pedantic_graphics)
151 Barrier_Join(&position_barrier); //Make the graphics continue
157 //Compute forces by spawning threads, each thread gets a sub system
158 for (unsigned i = 0; i < options.num_threads; ++i)
160 if (pthread_create(worker_thread+i, NULL, Force_Thread, (void*)(sub_system+i)) != 0)
162 perror("In compute thread, couldn't create worker thread (force)");
169 for (unsigned i = 0; i < options.num_threads; ++i)
170 pthread_join(worker_thread[i], NULL);
173 // If required, wait for graphics to finish drawing
174 if (options.draw_graphics && options.pedantic_graphics)
175 Barrier_Wait(&graphics_barrier);
178 Barrier_Enter(&position_barrier);
180 //Compute positions by spawning a bunch of threads to do it
181 for (unsigned i = 0; i < options.num_threads; ++i)
183 if (pthread_create(worker_thread+i, NULL, Position_Thread, (void*)(sub_system+i)) != 0)
185 perror("In compute thread, couldn't create worker thread (position)");
192 for (unsigned i = 0; i < options.num_threads; ++i)
193 pthread_join(worker_thread[i], NULL);
196 StepFunction(s); // Execute single threaded stuff
201 #endif //PERSISTENT_THREADS
206 * @function BeforeDraw
207 * @purpose Called in graphics thread before the draw loop
208 * When --pedantic-graphics enabled, will wait for position computations to finish before drawing
209 * Otherwise does nothing
211 * This originally seemed like a good place to put the code now in StepFunction(), since only one thread runs this
212 * But then I realised that the graphics might be disabled,
213 * and there was no point having a thread that only existed to call that code.
215 * So I changed it to the horrible solution that I currently have.
220 //printf("BEFORE DRAW\n");
221 if (!options.pedantic_graphics)
224 //printf("Graphics thread waits on position barrier\n");
225 Barrier_Wait(&position_barrier);
226 //printf("\tGraphics thread wakes up\n");
227 Barrier_Enter(&graphics_barrier);
231 * @function AfterDraw
232 * @purpose Called in graphics thread after the draw loop
233 * When --pedantic-graphics is supplied, will signal computation threads that drawing is finished
234 * So that positions can be safely altered
235 * Otherwise does nothing
239 //universe.steps += 1;
240 if (!options.pedantic_graphics)
242 Barrier_Join(&graphics_barrier);
246 #ifdef PERSISTENT_THREADS
249 * @function Worker_Thread
250 * @purpose Thread - A self contained worker thread to compute a particular sub system of bodies
252 * This is the "smart" way to do it, because threads are only created once, and compute both force and position.
253 * The greatest difficulty with pthreads is getting a *single* thread from the team to execute certain code
254 * (ie: The stuff in StepFunction()).
255 * With the "continuously respawning threads of stupidity" approach,
256 * because there is one "master" thread (not necessarilly the main thread... don't get confused now)
257 * to keep respawning the workers, the single threaded code can just be executed in the master thread.
259 * With this approach, I have created a hacky solution so that the *last* thread to leave the position barrier gets to call StepFunction.
262 void * Worker_Thread(void * arg)
264 System * s = (System*)(arg); // This is mainly to save typing the RHS a lot of times
266 // Each thread runs until the whole program is supposed to end
267 while (!ExitCondition())
271 System_Forces(s, &universe); // Each thread computes the forces for its share of bodies
273 // Do not confuse with "Barrier_Wait".
274 // Barrier_Wait does not affect the barrier; it just waits for it
275 // Barrier_Join actively updates the state of the barrier, and wakes up sleeping threads if required.
277 Barrier_Join(&force_barrier); // All threads must reach here before moving on.
278 if (ExitCondition()) return NULL;
281 //fprintf(stderr,"Thread %p - force barrier finished\n", arg);
282 //printf("Computed ALL forces\n");
285 // If required, wait for the graphics to finish drawing stuff
286 if (options.draw_graphics && options.pedantic_graphics)
288 //printf("Worker %p waits on graphics barrier\n", arg);
289 Barrier_Wait(&graphics_barrier);
290 //printf("\tWorker %p wakes up after graphics barrier\n", arg);
291 if (ExitCondition()) return NULL;
296 Barrier_Enter(&position_barrier);
297 System_Positions(s); // Each thread updates the positions for its share of bodies
300 // Barrier_JoinCall behaves in the same way as Barrier_Join, except the *last* thread
301 // (ie: the one that wakes up the others) also calls the function with arguments given.
302 Barrier_JoinCall(&position_barrier, StepFunction, (void*)(&universe));
303 if (ExitCondition()) return NULL;
304 //Barrier_Join(&position_barrier);
306 // All threads have computed positions, and *one* thread calls StepFunction()
309 QuitProgram(false); // Set the run state of the program
313 #endif //PERSISTENT_THREADS
316 * @function Force_Thread
317 * @purpose Thread - Calculates the forces on objects in a System
318 * @param s - Can be cast to the System*
320 void * Force_Thread(void * s)
322 //System_ForcePair * pair = (System_ForcePair*)s;
325 System_Forces(s, &universe); //Simple wrapper
326 //printf("Force_Thread waits\n");
333 * @function Position_Thread
334 * @purpose Thread - Calculates the positions of objects in a System
335 * @param s - Can be cast to the System*
337 void * Position_Thread(void * s)
340 System_Positions((System*)s); // Simple wrapper
341 Barrier_Join(&position_barrier); // This needed so that graphics will wait
347 * @function QuitProgram
348 * @purpose This function can be called in any thread to signal all threads to exit
349 * Repeated calls to this function have no effect
351 * All threads periodically call ExitCondition(), which will return true if the program should exit.
352 * One (not the only way) to return true is if this function has been called.
353 * Threads will call this function if they detect ExitCondition() is true. Only the first call has any effect.
355 inline void QuitProgram(bool error)
357 //If already quitting, don't do anything
358 if (runstate == QUIT || runstate == QUIT_ERROR)
363 // set the runstate (checked in ExitCondition())
365 pthread_mutex_lock(&mutex_runstate); // aquire mutex
367 runstate = QUIT_ERROR; // Program is exiting due to an error
369 runstate = QUIT; // Program is exiting naturally
370 pthread_mutex_unlock(&mutex_runstate); //release mutex
376 * @function Thread_Cleanup
377 * @purpose Will be called in the *main* thread when exit() is called
378 * Ensures working threads will exit, and waits for them to finish.
379 * Then waits for them to finish.
380 * Also frees memory associated with the worker threads.
382 void Thread_Cleanup(void)
386 // Threads recheck the exit condition whenever they leave a barrier.
387 // These calls will stop any threads waiting forever in a barrier for threads that exited before getting to the barrier.
388 Barrier_ForceExit(&force_barrier);
389 Barrier_ForceExit(&position_barrier);
392 if (options.draw_graphics) // If the graphics are enabled...
394 // Then there is a computation thread, since graphics are done in the main thread
395 pthread_join(compute_thread, NULL);
398 #ifdef PERSISTENT_THREADS
399 for (unsigned i = 0; i < options.num_threads-1; ++i)
401 pthread_join(worker_thread[i], NULL);
404 // All other worker threads (if they were spawned) are terminated in Compute_Thread
405 #endif //PERSISTENT_THREADS
407 // Scary memory management here.
408 if (worker_thread != NULL)
410 if (sub_system != NULL)
412 worker_thread = NULL;
419 * @function Simulation_Run
420 * @purpose Initialise and start the simulation. Will be called in the main thread.
421 * Replaces the single-threaded macro that does nothing, and sets up the graphics and computation threads
422 * @param argc - Number of arguments - Passed to Graphics_Run if needed
423 * @param argv - Argument strings - Passed to Graphics_Run if needed
425 void Simulation_Run(int argc, char ** argv)
427 atexit(Thread_Cleanup);
428 if (options.draw_graphics) // The graphics are enabled
430 // I have chosen to do graphics in the main thread in this case.
431 // A *single* seperate thread is spawned here to do computations.
432 // This computation thread will spawn any additional worker threads required.
433 if (pthread_create(&compute_thread, NULL, Compute_Thread, (void*)&universe) != 0)
435 perror("Error creating compute thread");
439 // This is run in the main thread
440 // It is effectively the graphics initialisation, followed by the glut loop
441 Graphics_Run(argc, argv);
443 // The main thread reaches here after leaving the glut loop when ExitCondition() returns true.
447 exit(EXIT_SUCCESS); // This is the main thread; use exit()
450 else //The graphics are disabled
452 // If graphics are disabled, there is no point spawning an extra thread.
453 // In this case, the *main* thread starts computations.
454 // Note that it will probably spawn additional worker threads (unless options.num_threads <= 1)
455 Compute_Thread((void*)(&universe));
466 * @function Allocate_Threads
467 * @purpose Helper function to allocate an array of pthread_t objects
468 * Handles all the pointless, er, "important" error checking that should be done
469 * @param n - Number of threads in the array
471 * WARNING: Remember to free() the array!!!
473 pthread_t * Allocate_Threads(unsigned n)
475 pthread_t * result = (pthread_t*)(calloc(n, sizeof(pthread_t)));
478 perror("Unable to allocate memory for threads");
486 * @function StepFunction
487 * @purpose Helper to perform stuff in a single thread every step, after position computations are done
488 * The reason this has void* all over the place is so that I can pass the function pointer (to horrible dragons and fiendish demons).
489 * @param arg - Can be cast to System* for which steps are to be updated
490 * Will always be (void*)(&universe). But I have been brainwashed into the "global variables are baaaaad" philosophy.
493 void * StepFunction(void * arg)
495 //fprintf(stderr, "StepFunction called\n");
496 System * s = (System*)(arg);
497 s->steps += 1; //Increment number of steps computed
499 if (options.verbosity != 0 && s->steps % options.verbosity == 0)