History

GSF.Core: Updated LogicalThread API to handle prioritization at the thread scheduler so that threads can be reprioritized based on the priority of their tasks.

StephenCWills committed 9 years ago

Commit d6482bb1 parent 3c892cb4 files · +153 −53Browse files

Showing the changes to Source/Libraries/GSF.Core.Shared/Threading (2 of 4 files). Show all

Unified · Split

2 files
  1. Source/Libraries/GSF.Core.Shared/Threading/LogicalThread.cs +65 −26
  2. Source/Libraries/GSF.Core.Shared/Threading/LogicalThreadScheduler.cs +83 −22
Source/Libraries/GSF.Core.Shared/Threading/LogicalThread.cs+65−26
@@ -158,7 +158,8 @@ namespace GSF.Threading · 157 unmodified lines
158158 private LogicalThreadScheduler m_scheduler;
159159 private ConcurrentQueue<Action>[] m_queues;
160160 private Dictionary<object, object> m_threadLocalStorage;
161− private int m_isActive;
161+ private ICancellationToken m_nextExecutionToken;
162+ private int m_activePriority;
162163
163164 private LogicalThreadStatistics m_statistics;
164165
@@ -170,16 +171,7 @@ namespace GSF.Threading · 5 unmodified lines
170171 /// Creates a new instance of the <see cref="LogicalThread"/> class.
171172 /// </summary>
172173 public LogicalThread()
173− : this(1)
174− {
175− }
176−
177− /// <summary>
178− /// Creates a new instance of the <see cref="LogicalThread"/> class.
179− /// </summary>
180− /// <param name="priorityLevels">The number of levels of priority supported by this logical thread.</param>
181− public LogicalThread(int priorityLevels)
182− : this(DefaultScheduler, priorityLevels)
174+ : this(DefaultScheduler)
183175 {
184176 }
185177
@@ -187,20 +179,16 @@ namespace GSF.Threading · 1 unmodified line
187179 /// Creates a new instance of the <see cref="LogicalThread"/> class.
188180 /// </summary>
189181 /// <param name="scheduler">The <see cref="LogicalThreadScheduler"/> that created this thread.</param>
190− /// <param name="priorityLevels">The number of levels of priority supported by this logical thread.</param>
191182 /// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception>
192− /// <exception cref="ArgumentException"><paramref name="priorityLevels"/> is less than or equal to zero.</exception>
193− internal LogicalThread(LogicalThreadScheduler scheduler, int priorityLevels)
183+ internal LogicalThread(LogicalThreadScheduler scheduler)
194184 {
195185 if ((object)scheduler == null)
196186 throw new ArgumentNullException(nameof(scheduler));
197187
198− if (priorityLevels <= 0)
199− throw new ArgumentException($"A logical thread must have at least one priority level.", nameof(priorityLevels));
200−
201188 m_scheduler = scheduler;
202− m_queues = new ConcurrentQueue<Action>[priorityLevels];
189+ m_queues = new ConcurrentQueue<Action>[PriorityLevels];
203190 m_threadLocalStorage = new Dictionary<object, object>();
191+ m_nextExecutionToken = new CancellationToken();
204192 m_statistics = new LogicalThreadStatistics();
205193
206194 for (int i = 0; i < m_queues.Length; i++)
@@ -219,7 +207,7 @@ namespace GSF.Threading · 12 unmodified lines
219207 {
220208 get
221209 {
222− return m_queues.Length;
210+ return m_scheduler.PriorityLevels;
223211 }
224212 }
225213
@@ -235,6 +223,49 @@ namespace GSF.Threading · 9 unmodified lines
235223 }
236224 }
237225
226+ /// <summary>
227+ /// Gets or sets the priority at which the
228+ /// logical thread is queued by the scheduler.
229+ /// </summary>
230+ internal int ActivePriority
231+ {
232+ get
233+ {
234+ return Interlocked.CompareExchange(ref m_activePriority, 0, 0);
235+ }
236+ set
237+ {
238+ Interlocked.Exchange(ref m_activePriority, value);
239+ }
240+ }
241+
242+ /// <summary>
243+ /// Gets the priority of the next action to be processed on this logical thread.
244+ /// </summary>
245+ internal int NextPriority
246+ {
247+ get
248+ {
249+ return PriorityLevels - m_queues.TakeWhile(queue => queue.IsEmpty).Count();
250+ }
251+ }
252+
253+ /// <summary>
254+ /// Gets or sets the cancellation token for the next time
255+ /// the thread's actions will be executed by the scheduler.
256+ /// </summary>
257+ internal ICancellationToken NextExecutionToken
258+ {
259+ get
260+ {
261+ return Interlocked.CompareExchange(ref m_nextExecutionToken, null, null);
262+ }
263+ set
264+ {
265+ Interlocked.Exchange(ref m_nextExecutionToken, value);
266+ }
267+ }
268+
238269 #endregion
239270
240271 #region [ Methods ]
@@ -260,7 +291,7 @@ namespace GSF.Threading · 19 unmodified lines
260291 throw new ArgumentException($"Priority {priority} is outside the range between 1 and {PriorityLevels}.", nameof(priority));
261292
262293 m_queues[PriorityLevels - priority].Enqueue(action);
263− m_scheduler.SignalItemHandler(this);
294+ m_scheduler.SignalItemHandler(this, priority);
264295 }
265296
266297 /// <summary>
@@ -305,20 +336,28 @@ namespace GSF.Threading · 38 unmodified lines
305336 }
306337
307338 /// <summary>
308− /// Activates the thread if it is not already active.
339+ /// Attempts to activate the thread at the given priority.
309340 /// </summary>
310− /// <returns>True if this call to TryActivate caused the thread to be active.</returns>
311− internal bool TryActivate()
341+ /// <param name="priority">The priority at which to activate the thread.</param>
342+ /// <returns>True if the thread's priority needs to be changed to the given priority; false otherwise.</returns>
343+ internal bool TryActivate(int priority)
312344 {
313− return Interlocked.CompareExchange(ref m_isActive, 1, 0) == 0;
345+ // Always get the execution token before the
346+ // active priority to mitigate race conditions
347+ ICancellationToken nextExecutionToken = NextExecutionToken;
348+ int activePriority = ActivePriority;
349+ return (activePriority < priority) && nextExecutionToken.Cancel();
314350 }
315351
316352 /// <summary>
317− /// Deactivates the thread.
353+ /// Completely deactivates the thread, making it available for reactivation at any priority.
318354 /// </summary>
319355 internal void Deactivate()
320356 {
321− Interlocked.Exchange(ref m_isActive, 0);
357+ // Always update the active priority before the
358+ // execution token to mitigate race conditions
359+ ActivePriority = 0;
360+ NextExecutionToken = new CancellationToken();
322361 }
323362
324363 /// <summary>
Source/Libraries/GSF.Core.Shared/Threading/LogicalThreadScheduler.cs+83−22
@@ -47,7 +47,7 @@ namespace GSF.Threading · 46 unmodified lines
4747 public event EventHandler<EventArgs<Exception>> UnhandledException;
4848
4949 // Fields
50− private ConcurrentQueue<LogicalThread> m_logicalThreads;
50+ private ConcurrentQueue<Func<LogicalThread>>[] m_logicalThreadQueues;
5151 private int m_maxThreadCount;
5252 private int m_threadCount;
5353 private bool m_useBackgroundThreads;
@@ -59,11 +59,19 @@ namespace GSF.Threading · 5 unmodified lines
5959 /// <summary>
6060 /// Creates a new instance of the <see cref="LogicalThreadScheduler"/> class.
6161 /// </summary>
62− public LogicalThreadScheduler()
62+ /// <param name="priorityLevels">The number of levels of priority supported by this logical thread scheduler.</param>
63+ /// <exception cref="ArgumentException"><paramref name="priorityLevels"/> is less than or equal to zero.</exception>
64+ public LogicalThreadScheduler(int priorityLevels = 1)
6365 {
66+ if (priorityLevels < 1)
67+ throw new ArgumentException($"A logical thread scheduler must have at least one priority level.", nameof(priorityLevels));
68+
6469 m_maxThreadCount = Environment.ProcessorCount;
65− m_logicalThreads = new ConcurrentQueue<LogicalThread>();
70+ m_logicalThreadQueues = new ConcurrentQueue<Func<LogicalThread>>[priorityLevels];
6671 m_useBackgroundThreads = true;
72+
73+ for (int i = 0; i < priorityLevels; i++)
74+ m_logicalThreadQueues[i] = new ConcurrentQueue<Func<LogicalThread>>();
6775 }
6876
6977 #endregion
@@ -86,7 +94,7 @@ namespace GSF.Threading · 16 unmodified lines
8694 throw new ArgumentException("Max thread count must be greater than zero", nameof(value));
8795
8896 int diff = value - m_maxThreadCount;
89− int inactiveThreads = Math.Min(diff, m_logicalThreads.Count);
97+ int inactiveThreads = Math.Min(diff, m_logicalThreadQueues.Sum(queue => queue.Count));
9098
9199 Interlocked.Exchange(ref m_maxThreadCount, value);
92100
@@ -111,6 +119,18 @@ namespace GSF.Threading · 18 unmodified lines
111119 }
112120 }
113121
122+ /// <summary>
123+ /// Gets the number of levels of priority
124+ /// supported by this scheduler.
125+ /// </summary>
126+ public int PriorityLevels
127+ {
128+ get
129+ {
130+ return m_logicalThreadQueues.Length;
131+ }
132+ }
133+
114134 /// <summary>
115135 /// Gets the current number of active physical threads.
116136 /// </summary>
@@ -133,18 +153,7 @@ namespace GSF.Threading · 16 unmodified lines
133153 /// <returns>A new logical thread managed by this scheduler.</returns>
134154 public LogicalThread CreateThread()
135155 {
136− return CreateThread(1);
137− }
138−
139− /// <summary>
140− /// Creates a new logical thread whose
141− /// execution is managed by this scheduler.
142− /// </summary>
143− /// <param name="priorityLevels">The number of levels of priority supported by the logical thread.</param>
144− /// <returns>A new logical thread managed by this scheduler.</returns>
145− public LogicalThread CreateThread(int priorityLevels)
146− {
147− return new LogicalThread(this, priorityLevels);
156+ return new LogicalThread(this);
148157 }
149158
150159 /// <summary>
@@ -152,11 +161,12 @@ namespace GSF.Threading · 1 unmodified line
152161 /// thread has new actions to be processed.
153162 /// </summary>
154163 /// <param name="thread">The thread with new actions to be processed.</param>
155− internal void SignalItemHandler(LogicalThread thread)
164+ /// <param name="priority">The priority at which the thread is being signaled.</param>
165+ internal void SignalItemHandler(LogicalThread thread, int priority)
156166 {
157− if (thread.TryActivate())
167+ if (thread.TryActivate(priority))
158168 {
159− m_logicalThreads.Enqueue(thread);
169+ Enqueue(thread);
160170 ActivatePhysicalThread();
161171 }
162172 }
@@ -204,13 +214,19 @@ namespace GSF.Threading · 41 unmodified lines
204214 private void ProcessLogicalThreads()
205215 {
206216 Stopwatch stopwatch;
217+ Func<LogicalThread> accessor = null;
207218 LogicalThread thread;
208219 Action action;
209220
210221 stopwatch = new Stopwatch();
211222
212− while (ThreadCount <= MaxThreadCount && m_logicalThreads.TryDequeue(out thread))
223+ while (ThreadCount <= MaxThreadCount && (object)m_logicalThreadQueues.FirstOrDefault(queue => queue.TryDequeue(out accessor)) != null)
213224 {
225+ thread = accessor();
226+
227+ if ((object)thread == null)
228+ continue;
229+
214230 action = thread.Pull();
215231
216232 if ((object)action != null)
@@ -226,17 +242,62 @@ namespace GSF.Threading · 9 unmodified lines
226242 }
227243
228244 if (thread.HasAction)
229− m_logicalThreads.Enqueue(thread);
245+ Enqueue(thread);
230246 else
231247 thread.Deactivate();
232248 }
233249
234250 DeactivatePhysicalThread();
235251
236− if (!m_logicalThreads.IsEmpty)
252+ if (!m_logicalThreadQueues.All(queue => queue.IsEmpty))
237253 ActivatePhysicalThread();
238254 }
239255
256+ /// <summary>
257+ /// Queues the given thread for execution.
258+ /// </summary>
259+ /// <param name="thread">The thread to be queued for execution.</param>
260+ private void Enqueue(LogicalThread thread)
261+ {
262+ ICancellationToken executionToken;
263+ int activePriority;
264+ int nextPriority;
265+
266+ do
267+ {
268+ // Create the execution token to be used in the closure
269+ ICancellationToken nextExecutionToken = new CancellationToken();
270+
271+ // Always update the thread's active priority before
272+ // the execution token to mitigate race conditions
273+ nextPriority = thread.NextPriority;
274+ thread.ActivePriority = nextPriority;
275+ thread.NextExecutionToken = nextExecutionToken;
276+
277+ // Now that the action can be cancelled by another thread using the
278+ // new cancellation token, it should be safe to put it in the queue
279+ m_logicalThreadQueues[PriorityLevels - nextPriority].Enqueue(() =>
280+ {
281+ if (nextExecutionToken.Cancel())
282+ return thread;
283+
284+ return null;
285+ });
286+
287+ // Because enqueuing the thread is a multi-step process, we need to
288+ // double-check in case the thread's priority changed in the meantime
289+ activePriority = thread.ActivePriority;
290+ nextPriority = thread.NextPriority;
291+
292+ // We can use the cancellation token we just created because we only
293+ // really need to double-check the work that was done on this thread;
294+ // in other words, if another thread changed the priority in the
295+ // meantime, it can double-check its own work
296+ executionToken = nextExecutionToken;
297+ }
298+ while (activePriority != nextPriority && executionToken.Cancel());
299+ }
300+
240301 /// <summary>
241302 /// Attempts to execute the given action.
242303 /// </summary>

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t