radiate_core/domain/sync/
thread_pool.rs1use crossbeam::channel;
2use std::thread;
3use std::{
4 fmt::Debug,
5 sync::{Arc, OnceLock},
6};
7
8struct FixedThreadPool {
16 inner: Arc<ThreadPool>,
17}
18
19impl FixedThreadPool {
20 pub(self) fn instance(num_workers: usize) -> &'static FixedThreadPool {
26 static INSTANCE: OnceLock<FixedThreadPool> = OnceLock::new();
27
28 INSTANCE.get_or_init(|| FixedThreadPool {
29 inner: Arc::new(ThreadPool::new(num_workers)),
30 })
31 }
32}
33
34pub fn get_thread_pool(num_workers: usize) -> Arc<ThreadPool> {
35 Arc::clone(&FixedThreadPool::instance(num_workers).inner)
36}
37
38pub struct WorkResult<T> {
42 receiver: channel::Receiver<T>,
43}
44
45impl<T> WorkResult<T> {
46 pub fn new(rx: channel::Receiver<T>) -> Self {
47 WorkResult { receiver: rx }
48 }
49 pub fn result(&self) -> T {
52 self.receiver.recv().unwrap()
53 }
54}
55
56pub struct ThreadPool {
57 sender: channel::Sender<Message>,
58 workers: Vec<Worker>,
59}
60
61impl ThreadPool {
62 pub fn new(size: usize) -> Self {
66 let (sender, receiver) = channel::unbounded();
67
68 ThreadPool {
69 sender,
70 workers: (0..size)
71 .map(|id| Worker::new(id, receiver.clone()))
72 .collect(),
73 }
74 }
75
76 pub fn num_workers(&self) -> usize {
77 self.workers.len()
78 }
79
80 pub fn is_alive(&self) -> bool {
81 self.workers.iter().any(|worker| worker.is_alive())
82 }
83
84 pub fn submit<F>(&self, f: F)
109 where
110 F: FnOnce() + Send + 'static,
111 {
112 let job = Box::new(f);
113 self.sender.send(Message::Work(job)).unwrap();
114 }
115
116 pub fn submit_with_result<F, T>(&self, f: F) -> WorkResult<T>
136 where
137 F: FnOnce() -> T + Send + 'static,
138 T: Send + 'static,
139 {
140 let (tx, rx) = channel::bounded(1);
141 let job = Box::new(move || tx.send(f()).unwrap());
142
143 self.sender.send(Message::Work(job)).unwrap();
144
145 WorkResult { receiver: rx }
146 }
147}
148
149impl Drop for ThreadPool {
152 fn drop(&mut self) {
153 for _ in self.workers.iter() {
154 self.sender.send(Message::Terminate).unwrap();
155 }
156
157 for worker in self.workers.iter_mut() {
158 if let Some(thread) = worker.thread.take() {
159 thread.join().unwrap();
160 }
161 }
162
163 assert!(!self.is_alive());
164 }
165}
166
167type Job = Box<dyn FnOnce() + Send + 'static>;
169
170enum Message {
172 Work(Job),
173 Terminate,
174}
175
176struct Worker {
178 id: usize,
179 thread: Option<thread::JoinHandle<()>>,
180}
181
182impl Worker {
183 fn new(id: usize, receiver: channel::Receiver<Message>) -> Self {
187 Worker {
188 id,
189 thread: Some(thread::spawn(move || {
190 loop {
191 while let Ok(message) = receiver.recv() {
192 match message {
193 Message::Work(job) => job(),
194 Message::Terminate => return,
195 }
196 }
197 }
198 })),
199 }
200 }
201
202 pub fn is_alive(&self) -> bool {
205 self.thread.is_some()
206 }
207}
208
209impl Debug for Worker {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("Worker")
212 .field("id", &self.id)
213 .field("is_alive", &self.is_alive())
214 .finish()
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::WaitGroup;
222 use std::{
223 sync::{Mutex, mpsc},
224 time::{Duration, Instant},
225 };
226
227 #[test]
228 fn test_thread_pool_creation() {
229 let pool = ThreadPool::new(4);
230 assert!(pool.is_alive());
231 }
232
233 #[test]
234 fn test_basic_job_execution() {
235 let pool = ThreadPool::new(4);
236 let counter = Arc::new(Mutex::new(0));
237
238 for _ in 0..8 {
239 let counter = Arc::clone(&counter);
240 pool.submit(move || {
241 let mut num = counter.lock().unwrap();
242 *num += 1;
243 });
244 }
245
246 thread::sleep(Duration::from_secs(1));
248 assert_eq!(*counter.lock().unwrap(), 8);
249 }
250
251 #[test]
252 fn test_thread_pool() {
253 let pool = ThreadPool::new(4);
254
255 for i in 0..8 {
256 pool.submit(move || {
257 let start_time = std::time::SystemTime::now();
258 println!("Job {} started.", i);
259 thread::sleep(Duration::from_secs(1));
260 println!("Job {} finished in {:?}.", i, start_time.elapsed().unwrap());
261 });
262 }
263 }
264
265 #[test]
266 fn test_job_order() {
267 let pool = ThreadPool::new(2);
268 let results = Arc::new(Mutex::new(vec![]));
269
270 for i in 0..5 {
271 let results = Arc::clone(&results);
272 pool.submit(move || {
273 results.lock().unwrap().push(i);
274 });
275 }
276
277 thread::sleep(Duration::from_secs(1));
279 let mut results = results.lock().unwrap();
280 results.sort(); assert_eq!(*results, vec![0, 1, 2, 3, 4]);
282 }
283
284 #[test]
285 fn test_thread_pool_process() {
286 let pool = ThreadPool::new(4);
287
288 let results = pool.submit_with_result(|| {
289 let start_time = std::time::SystemTime::now();
290 println!("Job started.");
291 thread::sleep(Duration::from_secs(2));
292 println!("Job finished in {:?}.", start_time.elapsed().unwrap());
293 42
294 });
295
296 let result = results.result();
297 assert_eq!(result, 42);
298 }
299
300 #[test]
301 fn test_max_concurrent_jobs() {
302 let pool = ThreadPool::new(4);
303 let (tx, rx) = mpsc::channel();
304 let num_jobs = 20;
305 let start_time = Instant::now();
306
307 for i in 0..num_jobs {
309 let tx = tx.clone();
310 pool.submit(move || {
311 thread::sleep(Duration::from_millis(100));
312 tx.send(i).unwrap();
313 });
314 }
315
316 let mut results = vec![];
318 for _ in 0..num_jobs {
319 results.push(rx.recv().unwrap());
320 }
321
322 let elapsed = start_time.elapsed();
323 assert!(elapsed < Duration::from_secs(3));
324 assert_eq!(results.len(), num_jobs);
325 assert!(results.iter().all(|&x| x < num_jobs));
326 }
327
328 #[test]
329 fn tests_thread_pool_submit_with_result_returns_correct_order() {
330 let pool = ThreadPool::new(5);
331 let num_jobs = 10;
332 let mut work_results = vec![];
333
334 for i in 0..num_jobs {
335 let work_result = pool.submit_with_result(move || {
336 thread::sleep(Duration::from_millis(50 * (num_jobs - i) as u64));
337 i * i
338 });
339 work_results.push(work_result);
340 }
341
342 for (i, work_result) in work_results.into_iter().enumerate() {
343 let result = work_result.result();
344 assert_eq!(result, i * i);
345 }
346 }
347
348 #[test]
349 fn test_wait_group() {
350 let pool = ThreadPool::new(4);
351 let wg = WaitGroup::new();
352 let num_tasks = 10;
353 let total = Arc::new(Mutex::new(0));
354
355 for _ in 0..num_tasks {
356 let guard = wg.guard();
357 let total = Arc::clone(&total);
358 pool.submit(move || {
359 thread::sleep(Duration::from_millis(100));
360 let mut num = total.lock().unwrap();
361 *num += 1;
362 drop(guard);
363 });
364 }
365
366 {
368 let total = total.lock().unwrap();
369 assert_ne!(*total, num_tasks);
370 }
371
372 let total_tasks_waited_for = wg.wait();
373
374 let total = total.lock().unwrap();
376 assert_eq!(*total, num_tasks);
377 assert_eq!(total_tasks_waited_for, num_tasks);
378 }
379
380 #[test]
381 fn test_wait_group_zero_tasks() {
382 let wg = WaitGroup::new();
383 let total_tasks_waited_for = wg.wait();
384 assert_eq!(total_tasks_waited_for, 0);
385 }
386}