f59b451228e37359697159a50204760395774ddc
[matches/honours.git] / course / semester2 / pprog / assignment1 / mthread / nbody.c
1 /**
2  * @file nbody.c
3  * @purpose Implentation of multi-threaded N-Body simulator using pthreads
4  * @author Sam Moore (20503628) - 2012
5  */
6
7 #include "nbody.h" // Declarations
8 #include "../single-thread/nbody.c" // Include all functions from the single threaded version
9
10 #include "graphics.h" // For declaration of Graphics_Run only
11
12 // --- Variable declarations --- //
13
14 pthread_t compute_thread; // The thread responsible for computations; it spawns worker threads
15         
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
21
22 pthread_mutex_t mutex_runstate; // Mutex around the runstate
23
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
27
28 /**
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)
33  */
34 void * Compute_Thread(void * arg)
35 {
36
37         System * s = (System*)(arg); //cast argument to a System*
38
39
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;
43
44         // Do a sanity check; there is no point spawning more threads than bodies.
45         if (options.num_threads > s->N)
46         {
47                 fprintf(stderr, 
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;
51         }
52         
53         if (options.num_threads > 1) // Allocate worker threads and sub systems, as long as there would be more than 1
54         {
55                 worker_thread = (pthread_t*)(calloc(options.num_threads, sizeof(pthread_t)));
56                 if (worker_thread == NULL)
57                 {
58                         perror("Couldn't allocate array of worker threads");
59                         QuitProgram(true);
60                         pthread_exit(NULL);
61                 }
62                 sub_system = (System*)(calloc(options.num_threads, sizeof(System)));
63                 if (sub_system == NULL)
64                 {
65                         perror("Couldn't allocate array of systems for worker threads to use");
66                         QuitProgram(true);
67                         pthread_exit(NULL);
68                 }
69
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)
74                 {
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
80
81                 }
82         }
83
84
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.
88
89         // The main computation loop
90         while (true)
91         {
92                 //Check whether the program should quit due to steps being computed, or a timeout
93                 if (ExitCondition())
94                 {
95                         QuitProgram(false);
96                         pthread_exit(NULL);
97                 }
98
99                 if (options.verbosity != 0 && universe.steps % options.verbosity == 1)
100                 {
101                         DisplayStatistics();
102                 }
103
104
105                 if (options.num_threads <= 1)
106                 {
107                         // Just do everything in this thread 
108                         System_Forces(s, s);
109                         System_Positions(s);
110                         continue;
111                 }
112
113                 
114
115                 
116                 workers_busy = options.num_threads; //All threads working
117
118                 //Compute forces
119                 for (unsigned i = 0; i < options.num_threads; ++i)
120                 {
121                         if (pthread_create(worker_thread+i, &attr, Force_Thread, (void*)(sub_system+i)) != 0)
122                         {
123                                 perror("In compute thread, couldn't create worker thread (force)");
124                                 QuitProgram(true);
125                                 pthread_exit(NULL);
126                         }       
127                 }
128
129
130
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);
136
137                 //All the forces are now computed
138
139                 // Wait for graphics to finish drawing the last step
140                 if (options.pedantic_graphics && options.draw_graphics)
141                 {
142                         pthread_mutex_lock(&mutex_graphics);
143                         while (graphics_busy)
144                                 pthread_cond_wait(&graphics_cv, &mutex_graphics);
145                         pthread_mutex_unlock(&mutex_graphics);
146                 }
147                 
148                 workers_busy = options.num_threads; //All threads working
149
150                 //Compute positions
151                 for (unsigned i = 0; i < options.num_threads; ++i)
152                 {
153                         if (pthread_create(worker_thread+i, &attr, Position_Thread, (void*)(sub_system+i)) != 0)
154                         {
155                                 perror("In compute thread, couldn't create worker thread (position)");
156                                 QuitProgram(true);
157                                 pthread_exit(NULL);
158                         }       
159                 }
160
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);
166
167                 //Update number of steps computed
168                 universe.steps += 1;
169
170
171                 // Signal graphics thread to draw bodies
172                 if (options.pedantic_graphics && options.draw_graphics)
173                 {
174                         pthread_mutex_lock(&mutex_graphics);
175                         graphics_busy = true;
176                         pthread_cond_signal(&graphics_cv);
177                         pthread_mutex_unlock(&mutex_graphics);
178                 }
179
180         }
181 }
182
183 /**
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
188  */
189 void BeforeDraw()
190 {
191         if (!options.pedantic_graphics)
192                 return;
193         pthread_mutex_lock(&mutex_graphics);
194         while (!graphics_busy)
195                 pthread_cond_wait(&graphics_cv, &mutex_graphics);
196         pthread_mutex_unlock(&mutex_graphics);
197 }
198
199 /**
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
205  */
206 void AfterDraw()
207 {
208         if (!options.pedantic_graphics)
209                 return;
210         pthread_mutex_lock(&mutex_graphics);
211         graphics_busy = false;
212         pthread_cond_signal(&graphics_cv);
213         pthread_mutex_unlock(&mutex_graphics);
214 }
215
216 /**
217  * @function Force_Thread
218  * @purpose Thread - Calculates the forces on objects in a System
219  * @param s - Can be cast to the System*
220  */
221 void * Force_Thread(void * s)
222 {
223         
224         System_Forces((System*)s, &universe); //Simple wrapper
225
226         pthread_mutex_lock(&mutex_workers);
227         workers_busy -= 1;      // Count this thread as done
228         if (workers_busy == 0)
229         {
230                 pthread_cond_signal(&workers_done_cv); // All threads done; wake up the compute_thread
231         }
232         pthread_mutex_unlock(&mutex_workers);
233         return NULL;
234 }
235
236 /**
237  * @function Position_Thread
238  * @purpose Thread - Calculates the positions of objects in a System 
239  * @param s - Can be cast to the System*
240  */
241 void * Position_Thread(void * s)
242 {
243         
244         System_Positions((System*)s); // Simple wrapper
245
246         pthread_mutex_lock(&mutex_workers);
247         workers_busy -= 1; // Count this thread as done
248         if (workers_busy == 0)
249         {
250                 pthread_cond_signal(&workers_done_cv); //All threads done; wake up the compute_thread
251         }
252         pthread_mutex_unlock(&mutex_workers);
253         return NULL;
254 }       
255
256 /**
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)
262  */
263 void QuitProgram(bool error)
264 {
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;
270         else
271                 runstate = QUIT;
272         pthread_mutex_unlock(&mutex_runstate); //release mutex
273 }
274
275 /**
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.   
281  */
282 void Thread_Cleanup(void)
283 {
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);
287         free(worker_thread);
288         free(sub_system);
289 }
290
291
292 /**
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
298  */
299 void Simulation_Run(int argc, char ** argv)
300 {
301         atexit(Thread_Cleanup);
302
303         if (options.draw_graphics)
304         {
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)
308                 {
309                         perror("Error creating compute thread");
310                         exit(EXIT_FAILURE);
311                 }
312                 Graphics_Run(argc, argv);
313         }
314         else
315                 Compute_Thread((void*)(&universe)); // Graphics are disabled, so do computations in the main thread
316 }

UCC git Repository :: git.ucc.asn.au