/* * Copyright 2016 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using Microsoft.Extensions.Logging; using System.Runtime.InteropServices; namespace CartographerSharp.Common.Threading; /// /// Interface for thread pool implementations. /// public abstract class ThreadPoolInterface { /// /// Schedules a task for execution. /// /// The task to schedule /// A weak reference to the task public abstract WeakReference Schedule(Task task); /// /// Executes a task. /// protected static void Execute(Task task) { task.Execute(); } /// /// Sets the thread pool for a task. /// protected void SetThreadPool(Task task) { task.SetThreadPool(this); } /// /// Notifies that dependencies of a task are completed. /// internal abstract void NotifyDependenciesCompleted(Task task); } /// /// A fixed number of threads working on tasks. Adding a task does not block. /// Tasks may be added whether or not their dependencies are completed. /// When all dependencies of a task are completed, it is queued up for execution /// in a background thread. The queue must be empty before calling the destructor. /// The thread pool will then wait for the currently executing work items to finish /// and then destroy the threads. /// public partial class ThreadPool : ThreadPoolInterface, IDisposable { private readonly object _lock = new(); private readonly List _pool = []; private readonly Queue _taskQueue = new(); private readonly Dictionary _tasksNotReady = []; private bool _running = true; private bool _disposed = false; /// /// Creates a new thread pool with the specified number of threads. /// public ThreadPool(int numThreads) { if (numThreads <= 0) { throw new ArgumentException("ThreadPool requires a positive num_threads!", nameof(numThreads)); } lock (_lock) { for (int i = 0; i < numThreads; i++) { var thread = new Thread(DoWork) { IsBackground = false, Name = $"CartographerThreadPool-{i}" }; _pool.Add(thread); thread.Start(); } } } /// /// Schedules a task for execution. /// public override WeakReference Schedule(Task task) { ArgumentNullException.ThrowIfNull(task); lock (_lock) { if (!_running) { throw new InvalidOperationException("ThreadPool is not running"); } if (_tasksNotReady.ContainsKey(task)) { throw new InvalidOperationException("Schedule called twice for the same task"); } _tasksNotReady[task] = task; } SetThreadPool(task); return new WeakReference(task); } /// /// Notifies that dependencies of a task are completed. /// internal override void NotifyDependenciesCompleted(Task task) { lock (_lock) { if (!_tasksNotReady.TryGetValue(task, out var storedTask)) { throw new InvalidOperationException("Task not found in tasks_not_ready"); } _taskQueue.Enqueue(storedTask); _tasksNotReady.Remove(task); Monitor.PulseAll(_lock); } } /// /// Worker thread method. /// private void DoWork() { Thread.BeginThreadAffinity(); try { while (true) { Task? task = null; lock (_lock) { while (_taskQueue.Count == 0 && _running) { Monitor.Wait(_lock); } if (!_running && _taskQueue.Count == 0) { return; } if (_taskQueue.Count > 0) { task = _taskQueue.Dequeue(); } } if (task != null) { if (task.GetState() != TaskState.DependenciesCompleted) { continue; } Execute(task); } } } finally { Thread.EndThreadAffinity(); } } /// /// Disposes the thread pool, waiting for all tasks to complete. /// public void Dispose() { if (_disposed) { return; } lock (_lock) { if (!_running) { return; } _running = false; Monitor.PulseAll(_lock); } foreach (var thread in _pool) { thread.Join(); } _disposed = true; GC.SuppressFinalize(this); } [LibraryImport("libc", SetLastError = true)] private static partial int nice(int inc); }