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 // --- Variable declarations --- //
12 pthread_t compute_thread; // The thread responsible for computations; it spawns worker threads
14 pthread_t * worker_thread = NULL; //Array of worker threads responsible for Force and Position updates
15 System * sub_system = NULL; //Array of Systems used to divide up the main "universe" System for worker threads
16 pthread_mutex_t mutex_workers; // Mutex used for the barrier between Force and Position updates
17 pthread_cond_t workers_done_cv; // Conditional used for the barrier between Force and Position updates
18 unsigned workers_busy; // Number of workers currently doing something
20 pthread_mutex_t mutex_runstate; // Mutex around the runstate
25 * @function Compute_Thread
26 * @purpose Thread - Continuously computes steps for a system of bodies. Seperate from graphics functions.
27 * Spawns worker threads to divide up computation.
28 * @param arg - Can be cast to the System for which computations are performed (ie: &universe)
30 void * Compute_Thread(void * arg)
33 System * s = (System*)(arg); //cast argument to a System*
36 // If no number of threads provided, use the default value, unless someone changed that to a stupid value
37 if (options.num_threads <= 0)
38 options.num_threads = (DEFAULT_WORKING_THREADS > 1) ? DEFAULT_WORKING_THREADS : 1;
40 // Do a sanity check; there is no point spawning more threads than bodies.
41 if (options.num_threads > s->N)
44 "Warning: Using %u threads instead of %u specified, because there are only %u bodies to simulate!\n",
45 s->N, options.num_threads, s->N);
46 options.num_threads = s->N;
49 if (options.num_threads > 1) // Allocate worker threads and sub systems, as long as there would be more than 1
51 worker_thread = (pthread_t*)(calloc(options.num_threads, sizeof(pthread_t)));
52 if (worker_thread == NULL)
54 perror("Couldn't allocate array of worker threads");
58 sub_system = (System*)(calloc(options.num_threads, sizeof(System)));
59 if (sub_system == NULL)
61 perror("Couldn't allocate array of systems for worker threads to use");
66 // Divide up the Body array owned by s into options.num_threads arrays, one for each worker thread
67 unsigned bodies_per_system = (s->N) / options.num_threads;
68 unsigned remainder = (s->N) % options.num_threads;
69 for (unsigned i = 0; i < options.num_threads; ++i)
71 sub_system[i].body = (s->body)+(i*bodies_per_system);
72 sub_system[i].N = bodies_per_system;
73 sub_system[i].steps = 0;
74 if (i == options.num_threads - 1)
75 sub_system[i].N += remainder; // The last thread gets the remainder
81 pthread_attr_t attr; //thread attribute for the workers.
82 pthread_attr_init(&attr);
83 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //Needs to be detached, so that memory can be reused.
85 // The main computation loop
89 if (runstate != RUN) pthread_exit(NULL); //Check whether the main thread wants to exit
93 //Check whether the program should quit due to steps being computed, or a timeout
94 if (options.timeout > 0.0)
96 if ((unsigned)(time(NULL) - options.start_time.tv_sec) >= options.timeout)
100 if (options.num_steps > 0 && universe.steps > options.num_steps)
103 if (options.draw_graphics == false && options.verbosity != 0
104 && universe.steps % options.verbosity == 1)
110 if (options.num_threads <= 1)
112 // Just do everything in this thread
121 workers_busy = options.num_threads; //All threads working
124 for (unsigned i = 0; i < options.num_threads; ++i)
126 if (pthread_create(worker_thread+i, &attr, Force_Thread, (void*)(sub_system+i)) != 0)
128 perror("In compute thread, couldn't create worker thread (force)");
136 //Barrier - Wait for forces to be computed
137 pthread_mutex_lock(&mutex_workers);
138 while (workers_busy > 0)
139 pthread_cond_wait(&workers_done_cv, &mutex_workers);
140 pthread_mutex_unlock(&mutex_workers);
142 //All the forces are now computed
144 workers_busy = options.num_threads; //All threads working
147 for (unsigned i = 0; i < options.num_threads; ++i)
149 if (pthread_create(worker_thread+i, &attr, Position_Thread, (void*)(sub_system+i)) != 0)
151 perror("In compute thread, couldn't create worker thread (position)");
157 //Wait for positions to be computed
158 pthread_mutex_lock(&mutex_workers);
159 while (workers_busy > 0)
160 pthread_cond_wait(&workers_done_cv, &mutex_workers);
161 pthread_mutex_unlock(&mutex_workers);
163 //Update number of steps computed
172 * @function Force_Thread
173 * @purpose Thread - Calculates the forces on objects in a System
174 * @param s - Can be cast to the System*
176 void * Force_Thread(void * s)
179 System_Forces((System*)s, &universe); //Simple wrapper
181 pthread_mutex_lock(&mutex_workers);
182 workers_busy -= 1; // Count this thread as done
183 if (workers_busy == 0)
185 pthread_cond_signal(&workers_done_cv); // All threads done; wake up the compute_thread
187 pthread_mutex_unlock(&mutex_workers);
192 * @function Position_Thread
193 * @purpose Thread - Calculates the positions of objects in a System
194 * @param s - Can be cast to the System*
196 void * Position_Thread(void * s)
199 System_Positions((System*)s); // Simple wrapper
201 pthread_mutex_lock(&mutex_workers);
202 workers_busy -= 1; // Count this thread as done
203 if (workers_busy == 0)
205 pthread_cond_signal(&workers_done_cv); //All threads done; wake up the compute_thread
207 pthread_mutex_unlock(&mutex_workers);
212 * @function QuitProgram
213 * @purpose This function can either be called by the main thread in order to signal other threads
214 * that it wants to exit. The main thread then calls pthread_join before exiting.
215 * It can also be called by a child thread to request the main thread to exit.
216 * It is only used this way if there is an unrecovarable error (ie: Can't allocate memory in a child thread)
218 void QuitProgram(bool error)
221 pthread_mutex_lock(&mutex_runstate); // aquire mutex
222 if (error) // set the runstate
223 runstate = QUIT_ERROR;
226 pthread_mutex_unlock(&mutex_runstate); //release mutex
230 * @function Thread_Cleanup
231 * @purpose Will be called in the main thread when exit() is called
232 * Automatically tells all other threads to quit (if they haven't already been told)
233 * Then waits for them to finish.
234 * Also frees memory associated with the worker threads.
236 void Thread_Cleanup(void)
238 if (runstate == RUN) // If this is true, as far as child threads are concerned, the simulation is still running
239 QuitProgram(false); // So call QuitProgram which will set runstate, and cause child threads to exit
240 pthread_join(compute_thread, NULL);
247 * @function Simulation_Run
248 * @purpose Initialise and start the simulation. Will be called in the main thread.
249 * Replaces the single-threaded macro that does nothing, and sets up the compute thread
251 void Simulation_Run()
253 atexit(Thread_Cleanup);
255 if (options.draw_graphics == false)
257 Compute_Thread((void*)(&universe));
259 // If there are no graphics, run the computation loop in the main thread
260 // The call to pthread_exit(NULL) in the computation loop ends the main thread
262 // This means the main function never calls the Graphics_Run function
263 // Without this condition here, Graphics_Display would essentially be running a busy loop in the main thread
265 fprintf(stderr,"Should not see this\n");
269 // Create a thread to handle computation loop
270 if (pthread_create(&compute_thread, NULL, Compute_Thread, (void*)&universe) != 0)
272 perror("Error creating compute thread");