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
12 // --- Variable declarations --- //
14 pthread_t compute_thread; // The thread responsible for computations; it spawns worker threads
16 pthread_t * worker_thread = NULL; //Array of worker threads responsible for Force and Position updates
17 System * sub_system = NULL; //Array of Systems used to divide up the main "universe" System for worker threads
18 pthread_mutex_t mutex_workers; // Mutex used for the barrier between Force and Position updates
19 pthread_cond_t workers_done_cv; // Conditional used for the barrier between Force and Position updates
20 unsigned workers_busy; // Number of workers currently doing something
22 pthread_mutex_t mutex_runstate; // Mutex around the runstate
24 pthread_mutex_t mutex_graphics; //Mutex between graphics and computation
25 pthread_cond_t graphics_cv; //Condition used to start graphics or computation thread from the other
26 bool graphics_busy = false; // Indicate if graphics is currently drawing
29 * @function Compute_Thread
30 * @purpose Thread - Continuously computes steps for a system of bodies. Seperate from graphics functions.
31 * Spawns worker threads to divide up computation.
32 * @param arg - Can be cast to the System for which computations are performed (ie: &universe)
34 void * Compute_Thread(void * arg)
37 System * s = (System*)(arg); //cast argument to a System*
40 // If no number of threads provided, use the default value, unless someone changed that to a stupid value
41 if (options.num_threads <= 0)
42 options.num_threads = (DEFAULT_WORKING_THREADS > 1) ? DEFAULT_WORKING_THREADS : 1;
44 // Do a sanity check; there is no point spawning more threads than bodies.
45 if (options.num_threads > s->N)
48 "Warning: Using %u threads instead of %u specified, because there are only %u bodies to simulate!\n",
49 s->N, options.num_threads, s->N);
50 options.num_threads = s->N;
53 if (options.num_threads > 1) // Allocate worker threads and sub systems, as long as there would be more than 1
55 worker_thread = (pthread_t*)(calloc(options.num_threads, sizeof(pthread_t)));
56 if (worker_thread == NULL)
58 perror("Couldn't allocate array of worker threads");
62 sub_system = (System*)(calloc(options.num_threads, sizeof(System)));
63 if (sub_system == NULL)
65 perror("Couldn't allocate array of systems for worker threads to use");
70 // Divide up the Body array owned by s into options.num_threads arrays, one for each worker thread
71 unsigned bodies_per_system = (s->N) / options.num_threads;
72 unsigned remainder = (s->N) % options.num_threads;
73 for (unsigned i = 0; i < options.num_threads; ++i)
75 sub_system[i].body = (s->body)+(i*bodies_per_system);
76 sub_system[i].N = bodies_per_system;
77 sub_system[i].steps = 0;
78 if (i == options.num_threads - 1)
79 sub_system[i].N += remainder; // The last thread gets the remainder
85 pthread_attr_t attr; //thread attribute for the workers.
86 pthread_attr_init(&attr);
87 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //Needs to be detached, so that memory can be reused.
89 // The main computation loop
92 //Check whether the program should quit due to steps being computed, or a timeout
99 if (options.verbosity != 0 && universe.steps % options.verbosity == 1)
105 if (options.num_threads <= 1)
107 // Just do everything in this thread
116 workers_busy = options.num_threads; //All threads working
119 for (unsigned i = 0; i < options.num_threads; ++i)
121 if (pthread_create(worker_thread+i, &attr, Force_Thread, (void*)(sub_system+i)) != 0)
123 perror("In compute thread, couldn't create worker thread (force)");
131 //Barrier - Wait for forces to be computed
132 pthread_mutex_lock(&mutex_workers);
133 while (workers_busy > 0)
134 pthread_cond_wait(&workers_done_cv, &mutex_workers);
135 pthread_mutex_unlock(&mutex_workers);
137 //All the forces are now computed
139 // Wait for graphics to finish drawing the last step
140 if (options.pedantic_graphics && options.draw_graphics)
142 pthread_mutex_lock(&mutex_graphics);
143 while (graphics_busy)
144 pthread_cond_wait(&graphics_cv, &mutex_graphics);
145 pthread_mutex_unlock(&mutex_graphics);
148 workers_busy = options.num_threads; //All threads working
151 for (unsigned i = 0; i < options.num_threads; ++i)
153 if (pthread_create(worker_thread+i, &attr, Position_Thread, (void*)(sub_system+i)) != 0)
155 perror("In compute thread, couldn't create worker thread (position)");
161 //Wait for positions to be computed
162 pthread_mutex_lock(&mutex_workers);
163 while (workers_busy > 0)
164 pthread_cond_wait(&workers_done_cv, &mutex_workers);
165 pthread_mutex_unlock(&mutex_workers);
167 //Update number of steps computed
171 // Signal graphics thread to draw bodies
172 if (options.pedantic_graphics && options.draw_graphics)
174 pthread_mutex_lock(&mutex_graphics);
175 graphics_busy = true;
176 pthread_cond_signal(&graphics_cv);
177 pthread_mutex_unlock(&mutex_graphics);
184 * @function BeforeDraw
185 * @purpose Called in graphics thread before the draw loop
186 * When --pedantic-graphics enabled, will wait for position computations to finish before drawing
187 * Otherwise does nothing
191 if (!options.pedantic_graphics)
193 pthread_mutex_lock(&mutex_graphics);
194 while (!graphics_busy)
195 pthread_cond_wait(&graphics_cv, &mutex_graphics);
196 pthread_mutex_unlock(&mutex_graphics);
200 * @function AfterDraw
201 * @purpose Called in graphics thread after the draw loop
202 * When --pedantic-graphics is supplied, will signal computation thread that drawing is finished
203 * So that positions can be safely altered
204 * Otherwise does nothing
208 if (!options.pedantic_graphics)
210 pthread_mutex_lock(&mutex_graphics);
211 graphics_busy = false;
212 pthread_cond_signal(&graphics_cv);
213 pthread_mutex_unlock(&mutex_graphics);
217 * @function Force_Thread
218 * @purpose Thread - Calculates the forces on objects in a System
219 * @param s - Can be cast to the System*
221 void * Force_Thread(void * s)
224 System_Forces((System*)s, &universe); //Simple wrapper
226 pthread_mutex_lock(&mutex_workers);
227 workers_busy -= 1; // Count this thread as done
228 if (workers_busy == 0)
230 pthread_cond_signal(&workers_done_cv); // All threads done; wake up the compute_thread
232 pthread_mutex_unlock(&mutex_workers);
237 * @function Position_Thread
238 * @purpose Thread - Calculates the positions of objects in a System
239 * @param s - Can be cast to the System*
241 void * Position_Thread(void * s)
244 System_Positions((System*)s); // Simple wrapper
246 pthread_mutex_lock(&mutex_workers);
247 workers_busy -= 1; // Count this thread as done
248 if (workers_busy == 0)
250 pthread_cond_signal(&workers_done_cv); //All threads done; wake up the compute_thread
252 pthread_mutex_unlock(&mutex_workers);
257 * @function QuitProgram
258 * @purpose This function can either be called by the main thread in order to signal other threads
259 * that it wants to exit. The main thread then calls pthread_join before exiting.
260 * It can also be called by a child thread to request the main thread to exit.
261 * It is only used this way if there is an unrecovarable error (ie: Can't allocate memory in a child thread)
263 void QuitProgram(bool error)
265 if (runstate == QUIT || runstate == QUIT_ERROR)
266 return; //Don't do anything if already quitting
267 pthread_mutex_lock(&mutex_runstate); // aquire mutex
268 if (error) // set the runstate
269 runstate = QUIT_ERROR;
272 pthread_mutex_unlock(&mutex_runstate); //release mutex
276 * @function Thread_Cleanup
277 * @purpose Will be called in the main thread when exit() is called
278 * Automatically tells all other threads to quit (if they haven't already been told)
279 * Then waits for them to finish.
280 * Also frees memory associated with the worker threads.
282 void Thread_Cleanup(void)
284 if (runstate == RUN) // If this is true, as far as child threads are concerned, the simulation is still running
285 QuitProgram(false); // So call QuitProgram which will set runstate, and cause child threads to exit
286 pthread_join(compute_thread, NULL);
293 * @function Simulation_Run
294 * @purpose Initialise and start the simulation. Will be called in the main thread.
295 * Replaces the single-threaded macro that does nothing, and sets up the compute thread
296 * @param argc - Number of arguments - Passed to Graphics_Run if needed
297 * @param argv - Argument strings - Passed to Graphics_Run if needed
299 void Simulation_Run(int argc, char ** argv)
301 atexit(Thread_Cleanup);
303 if (options.draw_graphics)
305 // The graphics are enabled, so create a thread to do computations
306 // Graphics are done in the main loop
307 if (pthread_create(&compute_thread, NULL, Compute_Thread, (void*)&universe) != 0)
309 perror("Error creating compute thread");
312 Graphics_Run(argc, argv);
315 Compute_Thread((void*)(&universe)); // Graphics are disabled, so do computations in the main thread