Parallel Programming - pthreads version
[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
19 pthread_mutex_t mutex_runstate; // Mutex around the runstate
20
21 Barrier worker_barrier;
22 Barrier graphics_barrier;
23
24
25 /**
26  * @function Compute_Thread
27  * @purpose Thread - Continuously computes steps for a system of bodies. Seperate from graphics functions.
28  *      Spawns worker threads to divide up computation.
29  * @param arg - Can be cast to the System for which computations are performed (ie: &universe)
30  */
31 void * Compute_Thread(void * arg)
32 {
33
34         System * s = (System*)(arg); //cast argument to a System*
35
36
37         // If no number of threads provided, use the default value, unless someone changed that to a stupid value
38         if (options.num_threads <= 0)
39                 options.num_threads = (DEFAULT_WORKING_THREADS > 1) ? DEFAULT_WORKING_THREADS : 1;
40
41         // Do a sanity check; there is no point spawning more threads than bodies.
42         if (options.num_threads > s->N)
43         {
44                 fprintf(stderr, 
45                         "Warning: Using %u threads instead of %u specified, because there are only %u bodies to simulate!\n",
46                         s->N, options.num_threads, s->N);       
47                 options.num_threads = s->N;
48         }
49         
50         if (options.num_threads > 1) // Allocate worker threads and sub systems, as long as there would be more than 1
51         {
52                 worker_thread = (pthread_t*)(calloc(options.num_threads, sizeof(pthread_t)));
53                 if (worker_thread == NULL)
54                 {
55                         perror("Couldn't allocate array of worker threads");
56                         QuitProgram(true);
57                         pthread_exit(NULL);
58                 }
59                 sub_system = (System*)(calloc(options.num_threads, sizeof(System)));
60                 if (sub_system == NULL)
61                 {
62                         perror("Couldn't allocate array of systems for worker threads to use");
63                         QuitProgram(true);
64                         pthread_exit(NULL);
65                 }
66
67                 // Divide up the Body array owned by s into options.num_threads arrays, one for each worker thread
68                 unsigned bodies_per_system = (s->N) / options.num_threads;
69                 unsigned remainder = (s->N) % options.num_threads;
70                 for (unsigned i = 0; i < options.num_threads; ++i)
71                 {
72                         sub_system[i].body = (s->body)+(i*bodies_per_system);
73                         sub_system[i].N = bodies_per_system;
74                         sub_system[i].steps = 0;
75                         if (i == options.num_threads - 1)
76                                 sub_system[i].N += remainder; // The last thread gets the remainder
77
78                 }
79         }
80
81
82         pthread_attr_t attr; //thread attribute for the workers. 
83         pthread_attr_init(&attr);
84         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //Needs to be detached, so that memory can be reused.
85
86         
87         
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                 //Compute forces
114                 for (unsigned i = 0; i < options.num_threads; ++i)
115                 {
116                         if (pthread_create(worker_thread+i, &attr, Force_Thread, (void*)(sub_system+i)) != 0)
117                         {
118                                 perror("In compute thread, couldn't create worker thread (force)");
119                                 QuitProgram(true);
120                                 pthread_exit(NULL);
121                         }       
122                         
123                 }
124
125
126                 Barrier_Wait(&worker_barrier);
127
128                 //All the forces are now computed
129
130                 if (options.draw_graphics && options.pedantic_graphics)
131                 {
132                         Barrier_Wait(&graphics_barrier);
133                         Barrier_Enter(&graphics_barrier);
134                 }
135
136                 //Compute positions
137                 for (unsigned i = 0; i < options.num_threads; ++i)
138                 {
139                         if (pthread_create(worker_thread+i, &attr, Position_Thread, (void*)(sub_system+i)) != 0)
140                         {
141                                 perror("In compute thread, couldn't create worker thread (position)");
142                                 QuitProgram(true);
143                                 pthread_exit(NULL);
144                         }       
145                 }
146
147                 //Wait for positions to be computed
148                 Barrier_Wait(&worker_barrier);
149
150
151                 //Update number of steps computed
152                 universe.steps += 1;
153
154                 if (options.draw_graphics && options.pedantic_graphics)
155                         Barrier_Leave(&graphics_barrier);
156
157
158         }
159 }
160
161 /**
162  * @function BeforeDraw
163  * @purpose Called in graphics thread before the draw loop
164  *      When --pedantic-graphics enabled, will wait for position computations to finish before drawing
165  *      Otherwise does nothing
166  */
167 void BeforeDraw()
168 {
169         if (!options.pedantic_graphics)
170                 return;
171         Barrier_Wait(&graphics_barrier);
172         Barrier_Enter(&graphics_barrier);
173 }
174
175 /**
176  * @function AfterDraw
177  * @purpose Called in graphics thread after the draw loop
178  *      When --pedantic-graphics is supplied, will signal computation thread that drawing is finished
179  *              So that positions can be safely altered
180  *      Otherwise does nothing
181  */
182 void AfterDraw()
183 {
184         if (!options.pedantic_graphics)
185                 return;
186         Barrier_Leave(&graphics_barrier);
187 }
188
189 /**
190  * @function Force_Thread
191  * @purpose Thread - Calculates the forces on objects in a System
192  * @param s - Can be cast to the System*
193  */
194 void * Force_Thread(void * s)
195 {
196         Barrier_Enter(&worker_barrier);
197
198         System_Forces((System*)s, &universe); //Simple wrapper
199
200         Barrier_Leave(&worker_barrier);
201
202         return NULL;
203 }
204
205 /**
206  * @function Position_Thread
207  * @purpose Thread - Calculates the positions of objects in a System 
208  * @param s - Can be cast to the System*
209  */
210 void * Position_Thread(void * s)
211 {
212         Barrier_Enter(&worker_barrier);
213         System_Positions((System*)s); // Simple wrapper
214         Barrier_Leave(&worker_barrier);
215         return NULL;
216 }       
217
218 /**
219  * @function QuitProgram
220  * @purpose This function can either be called by the main thread in order to signal other threads
221  *              that it wants to exit. The main thread then calls pthread_join before exiting.
222  *      It can also be called by a child thread to request the main thread to exit.
223  *      It is only used this way if there is an unrecovarable error (ie: Can't allocate memory in a child thread)
224  */
225 void QuitProgram(bool error)
226 {
227         if (runstate == QUIT || runstate == QUIT_ERROR)
228                 return; //Don't do anything if already quitting
229         pthread_mutex_lock(&mutex_runstate); // aquire mutex
230         if (error) // set the runstate
231                 runstate = QUIT_ERROR;
232         else
233                 runstate = QUIT;
234         pthread_mutex_unlock(&mutex_runstate); //release mutex
235 }
236
237 /**
238  * @function Thread_Cleanup
239  * @purpose Will be called in the main thread when exit() is called
240  *      Automatically tells all other threads to quit (if they haven't already been told) 
241  *      Then waits for them to finish.
242  *      Also frees memory associated with the worker threads.   
243  */
244 void Thread_Cleanup(void)
245 {
246         if (runstate == RUN) // If this is true, as far as child threads are concerned, the simulation is still running
247                 QuitProgram(false); // So call QuitProgram which will set runstate, and cause child threads to exit
248         pthread_join(compute_thread, NULL);
249         free(worker_thread);
250         free(sub_system);
251 }
252
253
254 /**
255  * @function Simulation_Run
256  * @purpose Initialise and start the simulation. Will be called in the main thread.
257  *      Replaces the single-threaded macro that does nothing, and sets up the compute thread
258  * @param argc - Number of arguments - Passed to Graphics_Run if needed
259  * @param argv - Argument strings - Passed to Graphics_Run if needed
260  */
261 void Simulation_Run(int argc, char ** argv)
262 {
263         atexit(Thread_Cleanup);
264
265         Barrier_Init(&worker_barrier);
266         Barrier_Init(&graphics_barrier);
267
268         if (options.draw_graphics)
269         {
270                 // The graphics are enabled, so create a thread to do computations
271                 // Graphics are done in the main loop
272                 if (pthread_create(&compute_thread, NULL, Compute_Thread, (void*)&universe) != 0)
273                 {
274                         perror("Error creating compute thread");
275                         exit(EXIT_FAILURE);
276                 }
277                 Graphics_Run(argc, argv);
278         }
279         else
280                 Compute_Thread((void*)(&universe)); // Graphics are disabled, so do computations in the main thread
281 }
282
283
284
285 void Barrier_Init(Barrier * b)
286 {
287         b->threads_busy = 0;
288 }       
289
290 void Barrier_Enter(Barrier * b)
291 {
292         pthread_mutex_lock(&(b->mutex));
293         b->threads_busy += 1;
294         pthread_mutex_unlock(&(b->mutex));
295 }
296
297 void Barrier_Leave(Barrier * b)
298 {
299         pthread_mutex_lock(&(b->mutex));
300         b->threads_busy -= 1;
301         if (b->threads_busy == 0)
302         {
303                 pthread_cond_signal(&(b->threads_done_cv));
304         }
305         pthread_mutex_unlock(&(b->mutex));
306 }
307
308 void Barrier_Wait(Barrier * b)
309 {
310         pthread_mutex_lock(&(b->mutex));
311         while (b->threads_busy > 0)
312                 pthread_cond_wait(&(b->threads_done_cv), &(b->mutex));
313         pthread_mutex_unlock(&(b->mutex));
314 }

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