2 * Copyright (c) 2010-2020 The RetroArch team
3 * Copyright (c) 2017 John Schember <john@nachtimwald.com>
5 * ---------------------------------------------------------------------------------------
6 * The following license statement only applies to this file (tpool.c).
7 * ---------------------------------------------------------------------------------------
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31 #include <rthreads/rthreads.h>
32 #include <rthreads/tpool.h>
34 /* Work object which will sit in a queue
35 * waiting for the pool to process it.
37 * It is a singly linked list acting as a FIFO queue. */
40 thread_func_t func; /* Function to be called. */
41 void *arg; /* Data to be passed to func. */
42 struct tpool_work *next; /* Next work item in the queue. */
44 typedef struct tpool_work tpool_work_t;
48 tpool_work_t *work_first; /* First work item in the work queue. */
49 tpool_work_t *work_last; /* Last work item in the work queue. */
50 slock_t *work_mutex; /* Mutex protecting inserting and removing work from the work queue. */
51 scond_t *work_cond; /* Conditional to signal when there is work to process. */
52 scond_t *working_cond; /* Conditional to signal when there is no work processing.
53 This will also signal when there are no threads running. */
54 size_t working_cnt; /* The number of threads processing work (Not waiting for work). */
55 size_t thread_cnt; /* Total number of threads within the pool. */
56 bool stop; /* Marker to tell the work threads to exit. */
59 static tpool_work_t *tpool_work_create(thread_func_t func, void *arg)
66 work = (tpool_work_t*)calloc(1, sizeof(*work));
73 static void tpool_work_destroy(tpool_work_t *work)
79 /* Pull the first work item out of the queue. */
80 static tpool_work_t *tpool_work_get(tpool_t *tp)
87 work = tp->work_first;
93 tp->work_first = NULL;
97 tp->work_first = work->next;
102 static void tpool_worker(void *arg)
104 tpool_work_t *work = NULL;
105 tpool_t *tp = (tpool_t*)arg;
109 slock_lock(tp->work_mutex);
110 /* Keep running until told to stop. */
114 /* If there is no work in the queue wait in the conditional until
115 * there is work to take. */
117 scond_wait(tp->work_cond, tp->work_mutex);
119 /* Try to pull work from the queue. */
120 work = tpool_work_get(tp);
122 slock_unlock(tp->work_mutex);
124 /* Call the work function and let it process.
126 * work can legitimately be NULL. Since multiple threads from the pool
127 * will wake when there is work, a thread might not get any work. 1
128 * piece of work and 2 threads, both will wake but with 1 only work 1
129 * will get the work and the other won't.
131 * working_cnt has been increment and work could be NULL. While it's
132 * not true there is work processing the thread is considered working
133 * because it's not waiting in the conditional. Pedantic but...
137 work->func(work->arg);
138 tpool_work_destroy(work);
141 slock_lock(tp->work_mutex);
143 /* Since we're in a lock no work can be added or removed form the queue.
144 * Also, the working_cnt can't be changed (except the thread holding the lock).
145 * At this point if there isn't any work processing and if there is no work
146 * signal this is the case. */
147 if (!tp->stop && tp->working_cnt == 0 && !tp->work_first)
148 scond_signal(tp->working_cond);
149 slock_unlock(tp->work_mutex);
153 if (tp->thread_cnt == 0)
154 scond_signal(tp->working_cond);
155 slock_unlock(tp->work_mutex);
158 tpool_t *tpool_create(size_t num)
167 tp = (tpool_t*)calloc(1, sizeof(*tp));
168 tp->thread_cnt = num;
170 tp->work_mutex = slock_new();
171 tp->work_cond = scond_new();
172 tp->working_cond = scond_new();
174 tp->work_first = NULL;
175 tp->work_last = NULL;
177 /* Create the requested number of thread and detach them. */
178 for (i = 0; i < num; i++)
180 thread = sthread_create(tpool_worker, tp);
181 sthread_detach(thread);
187 void tpool_destroy(tpool_t *tp)
195 /* Take all work out of the queue and destroy it. */
196 slock_lock(tp->work_mutex);
197 work = tp->work_first;
201 tpool_work_destroy(work);
205 /* Tell the worker threads to stop. */
207 scond_broadcast(tp->work_cond);
208 slock_unlock(tp->work_mutex);
210 /* Wait for all threads to stop. */
213 slock_free(tp->work_mutex);
214 scond_free(tp->work_cond);
215 scond_free(tp->working_cond);
220 bool tpool_add_work(tpool_t *tp, thread_func_t func, void *arg)
227 work = tpool_work_create(func, arg);
231 slock_lock(tp->work_mutex);
234 tp->work_first = work;
235 tp->work_last = tp->work_first;
239 tp->work_last->next = work;
240 tp->work_last = work;
243 scond_broadcast(tp->work_cond);
244 slock_unlock(tp->work_mutex);
249 void tpool_wait(tpool_t *tp)
254 slock_lock(tp->work_mutex);
258 /* working_cond is dual use. It signals when we're not stopping but the
259 * working_cnt is 0 indicating there isn't any work processing. If we
260 * are stopping it will trigger when there aren't any threads running. */
261 if ((!tp->stop && tp->working_cnt != 0) || (tp->stop && tp->thread_cnt != 0))
262 scond_wait(tp->working_cond, tp->work_mutex);
267 slock_unlock(tp->work_mutex);