2 * sgen-thread-pool.c: Threadpool for all concurrent GC work.
4 * Copyright (C) 2015 Xamarin Inc
6 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
12 #include "mono/sgen/sgen-gc.h"
13 #include "mono/sgen/sgen-thread-pool.h"
14 #include "mono/sgen/sgen-pointer-queue.h"
15 #include "mono/utils/mono-os-mutex.h"
16 #ifndef SGEN_WITHOUT_MONO
17 #include "mono/utils/mono-threads.h"
20 #define MAX_NUM_THREADS 8
22 static mono_mutex_t lock
;
23 static mono_cond_t work_cond
;
24 static mono_cond_t done_cond
;
26 static int threads_num
= 0;
27 static MonoNativeThreadId threads
[MAX_NUM_THREADS
];
29 /* Only accessed with the lock held. */
30 static SgenPointerQueue job_queue
;
32 static SgenThreadPoolThreadInitFunc thread_init_func
;
33 static SgenThreadPoolIdleJobFunc idle_job_func
;
34 static SgenThreadPoolContinueIdleJobFunc continue_idle_job_func
;
36 static volatile gboolean threadpool_shutdown
;
37 static volatile int threads_finished
= 0;
45 /* Assumes that the lock is held. */
46 static SgenThreadPoolJob
*
47 get_job_and_set_in_progress (void)
49 for (size_t i
= 0; i
< job_queue
.next_slot
; ++i
) {
50 SgenThreadPoolJob
*job
= (SgenThreadPoolJob
*)job_queue
.data
[i
];
51 if (job
->state
== STATE_WAITING
) {
52 job
->state
= STATE_IN_PROGRESS
;
59 /* Assumes that the lock is held. */
61 find_job_in_queue (SgenThreadPoolJob
*job
)
63 for (ssize_t i
= 0; i
< job_queue
.next_slot
; ++i
) {
64 if (job_queue
.data
[i
] == job
)
70 /* Assumes that the lock is held. */
72 remove_job (SgenThreadPoolJob
*job
)
75 SGEN_ASSERT (0, job
->state
== STATE_DONE
, "Why are we removing a job that's not done?");
76 index
= find_job_in_queue (job
);
77 SGEN_ASSERT (0, index
>= 0, "Why is the job we're trying to remove not in the queue?");
78 job_queue
.data
[index
] = NULL
;
79 sgen_pointer_queue_remove_nulls (&job_queue
);
80 sgen_thread_pool_job_free (job
);
84 continue_idle_job (void *thread_data
)
86 if (!continue_idle_job_func
)
88 return continue_idle_job_func (thread_data
);
91 static mono_native_thread_return_t
92 thread_func (void *thread_data
)
94 thread_init_func (thread_data
);
96 mono_os_mutex_lock (&lock
);
99 * It's important that we check the continue idle flag with the lock held.
100 * Suppose we didn't check with the lock held, and the result is FALSE. The
101 * main thread might then set continue idle and signal us before we can take
102 * the lock, and we'd lose the signal.
104 gboolean do_idle
= continue_idle_job (thread_data
);
105 SgenThreadPoolJob
*job
= get_job_and_set_in_progress ();
107 if (!job
&& !do_idle
&& !threadpool_shutdown
) {
109 * pthread_cond_wait() can return successfully despite the condition
110 * not being signalled, so we have to run this in a loop until we
111 * really have work to do.
113 mono_os_cond_wait (&work_cond
, &lock
);
117 mono_os_mutex_unlock (&lock
);
120 job
->func (thread_data
, job
);
122 mono_os_mutex_lock (&lock
);
124 SGEN_ASSERT (0, job
->state
== STATE_IN_PROGRESS
, "The job should still be in progress.");
125 job
->state
= STATE_DONE
;
128 * Only the main GC thread will ever wait on the done condition, so we don't
131 mono_os_cond_signal (&done_cond
);
132 } else if (do_idle
) {
133 SGEN_ASSERT (0, idle_job_func
, "Why do we have idle work when there's no idle job function?");
135 idle_job_func (thread_data
);
136 do_idle
= continue_idle_job (thread_data
);
137 } while (do_idle
&& !job_queue
.next_slot
);
139 mono_os_mutex_lock (&lock
);
142 mono_os_cond_signal (&done_cond
);
144 SGEN_ASSERT (0, threadpool_shutdown
, "Why did we unlock if no jobs and not shutting down?");
145 mono_os_mutex_lock (&lock
);
147 mono_os_cond_signal (&done_cond
);
148 mono_os_mutex_unlock (&lock
);
153 return (mono_native_thread_return_t
)0;
157 sgen_thread_pool_init (int num_threads
, SgenThreadPoolThreadInitFunc init_func
, SgenThreadPoolIdleJobFunc idle_func
, SgenThreadPoolContinueIdleJobFunc continue_idle_func
, void **thread_datas
)
161 threads_num
= (num_threads
< MAX_NUM_THREADS
) ? num_threads
: MAX_NUM_THREADS
;
163 mono_os_mutex_init (&lock
);
164 mono_os_cond_init (&work_cond
);
165 mono_os_cond_init (&done_cond
);
167 thread_init_func
= init_func
;
168 idle_job_func
= idle_func
;
169 continue_idle_job_func
= continue_idle_func
;
171 for (i
= 0; i
< threads_num
; i
++)
172 mono_native_thread_create (&threads
[i
], thread_func
, thread_datas
? thread_datas
[i
] : NULL
);
176 sgen_thread_pool_shutdown (void)
181 mono_os_mutex_lock (&lock
);
182 threadpool_shutdown
= TRUE
;
183 mono_os_cond_broadcast (&work_cond
);
184 while (threads_finished
< threads_num
)
185 mono_os_cond_wait (&done_cond
, &lock
);
186 mono_os_mutex_unlock (&lock
);
188 mono_os_mutex_destroy (&lock
);
189 mono_os_cond_destroy (&work_cond
);
190 mono_os_cond_destroy (&done_cond
);
194 sgen_thread_pool_job_alloc (const char *name
, SgenThreadPoolJobFunc func
, size_t size
)
196 SgenThreadPoolJob
*job
= (SgenThreadPoolJob
*)sgen_alloc_internal_dynamic (size
, INTERNAL_MEM_THREAD_POOL_JOB
, TRUE
);
199 job
->state
= STATE_WAITING
;
205 sgen_thread_pool_job_free (SgenThreadPoolJob
*job
)
207 sgen_free_internal_dynamic (job
, job
->size
, INTERNAL_MEM_THREAD_POOL_JOB
);
211 sgen_thread_pool_job_enqueue (SgenThreadPoolJob
*job
)
213 mono_os_mutex_lock (&lock
);
215 sgen_pointer_queue_add (&job_queue
, job
);
216 mono_os_cond_signal (&work_cond
);
218 mono_os_mutex_unlock (&lock
);
222 sgen_thread_pool_job_wait (SgenThreadPoolJob
*job
)
224 SGEN_ASSERT (0, job
, "Where's the job?");
226 mono_os_mutex_lock (&lock
);
228 while (find_job_in_queue (job
) >= 0)
229 mono_os_cond_wait (&done_cond
, &lock
);
231 mono_os_mutex_unlock (&lock
);
235 sgen_thread_pool_idle_signal (void)
237 SGEN_ASSERT (0, idle_job_func
, "Why are we signaling idle without an idle function?");
239 mono_os_mutex_lock (&lock
);
241 if (continue_idle_job_func (NULL
))
242 mono_os_cond_broadcast (&work_cond
);
244 mono_os_mutex_unlock (&lock
);
248 sgen_thread_pool_idle_wait (void)
250 SGEN_ASSERT (0, idle_job_func
, "Why are we waiting for idle without an idle function?");
252 mono_os_mutex_lock (&lock
);
254 while (continue_idle_job_func (NULL
))
255 mono_os_cond_wait (&done_cond
, &lock
);
257 mono_os_mutex_unlock (&lock
);
261 sgen_thread_pool_wait_for_all_jobs (void)
263 mono_os_mutex_lock (&lock
);
265 while (!sgen_pointer_queue_is_empty (&job_queue
))
266 mono_os_cond_wait (&done_cond
, &lock
);
268 mono_os_mutex_unlock (&lock
);
271 /* Return 0 if is not a thread pool thread or the thread number otherwise */
273 sgen_thread_pool_is_thread_pool_thread (MonoNativeThreadId some_thread
)
277 for (i
= 0; i
< threads_num
; i
++) {
278 if (some_thread
== threads
[i
])