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
158 private LogicalThreadScheduler m_scheduler;158 private LogicalThreadScheduler m_scheduler;
159 private ConcurrentQueue<Action>[] m_queues;159 private ConcurrentQueue<Action>[] m_queues;
160 private Dictionary<object, object> m_threadLocalStorage;160 private Dictionary<object, object> m_threadLocalStorage;
161 private int m_isActive;161 private ICancellationToken m_nextExecutionToken;
162 private int m_activePriority;
162 163
163 private LogicalThreadStatistics m_statistics;164 private LogicalThreadStatistics m_statistics;
164 165
@@ -170,16 +171,7 @@ namespace GSF.Threading · 5 unmodified lines
170 /// Creates a new instance of the <see cref="LogicalThread"/> class.171 /// Creates a new instance of the <see cref="LogicalThread"/> class.
171 /// </summary>172 /// </summary>
172 public LogicalThread()173 public LogicalThread()
173 : this(1)174 : this(DefaultScheduler)
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)
183 {175 {
184 }176 }
185 177
@@ -187,20 +179,16 @@ namespace GSF.Threading · 1 unmodified line
187 /// Creates a new instance of the <see cref="LogicalThread"/> class.179 /// Creates a new instance of the <see cref="LogicalThread"/> class.
188 /// </summary>180 /// </summary>
189 /// <param name="scheduler">The <see cref="LogicalThreadScheduler"/> that created this thread.</param>181 /// <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>
191 /// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception>182 /// <exception cref="ArgumentNullException"><paramref name="scheduler"/> is null.</exception>
192 /// <exception cref="ArgumentException"><paramref name="priorityLevels"/> is less than or equal to zero.</exception>183 internal LogicalThread(LogicalThreadScheduler scheduler)
193 internal LogicalThread(LogicalThreadScheduler scheduler, int priorityLevels)
194 {184 {
195 if ((object)scheduler == null)185 if ((object)scheduler == null)
196 throw new ArgumentNullException(nameof(scheduler));186 throw new ArgumentNullException(nameof(scheduler));
197 187
198 if (priorityLevels <= 0)
199 throw new ArgumentException($"A logical thread must have at least one priority level.", nameof(priorityLevels));
200
201 m_scheduler = scheduler;188 m_scheduler = scheduler;
202 m_queues = new ConcurrentQueue<Action>[priorityLevels];189 m_queues = new ConcurrentQueue<Action>[PriorityLevels];
203 m_threadLocalStorage = new Dictionary<object, object>();190 m_threadLocalStorage = new Dictionary<object, object>();
191 m_nextExecutionToken = new CancellationToken();
204 m_statistics = new LogicalThreadStatistics();192 m_statistics = new LogicalThreadStatistics();
205 193
206 for (int i = 0; i < m_queues.Length; i++)194 for (int i = 0; i < m_queues.Length; i++)
@@ -219,7 +207,7 @@ namespace GSF.Threading · 12 unmodified lines
219 {207 {
220 get208 get
221 {209 {
222 return m_queues.Length;210 return m_scheduler.PriorityLevels;
223 }211 }
224 }212 }
225 213
@@ -235,6 +223,49 @@ namespace GSF.Threading · 9 unmodified lines
235 }223 }
236 }224 }
237 225
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
238 #endregion269 #endregion
239 270
240 #region [ Methods ]271 #region [ Methods ]
@@ -260,7 +291,7 @@ namespace GSF.Threading · 19 unmodified lines
260 throw new ArgumentException($"Priority {priority} is outside the range between 1 and {PriorityLevels}.", nameof(priority));291 throw new ArgumentException($"Priority {priority} is outside the range between 1 and {PriorityLevels}.", nameof(priority));
261 292
262 m_queues[PriorityLevels - priority].Enqueue(action);293 m_queues[PriorityLevels - priority].Enqueue(action);
263 m_scheduler.SignalItemHandler(this);294 m_scheduler.SignalItemHandler(this, priority);
264 }295 }
265 296
266 /// <summary>297 /// <summary>
@@ -305,20 +336,28 @@ namespace GSF.Threading · 38 unmodified lines
305 }336 }
306 337
307 /// <summary>338 /// <summary>
308 /// Activates the thread if it is not already active.339 /// Attempts to activate the thread at the given priority.
309 /// </summary>340 /// </summary>
310 /// <returns>True if this call to TryActivate caused the thread to be active.</returns>341 /// <param name="priority">The priority at which to activate the thread.</param>
311 internal bool TryActivate()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)
312 {344 {
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();
314 }350 }
315 351
316 /// <summary>352 /// <summary>
317 /// Deactivates the thread.353 /// Completely deactivates the thread, making it available for reactivation at any priority.
318 /// </summary>354 /// </summary>
319 internal void Deactivate()355 internal void Deactivate()
320 {356 {
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();
322 }361 }
323 362
324 /// <summary>363 /// <summary>
Source/Libraries/GSF.Core.Shared/Threading/LogicalThreadScheduler.cs+83−22
@@ -47,7 +47,7 @@ namespace GSF.Threading · 46 unmodified lines
47 public event EventHandler<EventArgs<Exception>> UnhandledException;47 public event EventHandler<EventArgs<Exception>> UnhandledException;
48 48
49 // Fields49 // Fields
50 private ConcurrentQueue<LogicalThread> m_logicalThreads;50 private ConcurrentQueue<Func<LogicalThread>>[] m_logicalThreadQueues;
51 private int m_maxThreadCount;51 private int m_maxThreadCount;
52 private int m_threadCount;52 private int m_threadCount;
53 private bool m_useBackgroundThreads;53 private bool m_useBackgroundThreads;
@@ -59,11 +59,19 @@ namespace GSF.Threading · 5 unmodified lines
59 /// <summary>59 /// <summary>
60 /// Creates a new instance of the <see cref="LogicalThreadScheduler"/> class.60 /// Creates a new instance of the <see cref="LogicalThreadScheduler"/> class.
61 /// </summary>61 /// </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)
63 {65 {
66 if (priorityLevels < 1)
67 throw new ArgumentException($"A logical thread scheduler must have at least one priority level.", nameof(priorityLevels));
68
64 m_maxThreadCount = Environment.ProcessorCount;69 m_maxThreadCount = Environment.ProcessorCount;
65 m_logicalThreads = new ConcurrentQueue<LogicalThread>();70 m_logicalThreadQueues = new ConcurrentQueue<Func<LogicalThread>>[priorityLevels];
66 m_useBackgroundThreads = true;71 m_useBackgroundThreads = true;
72
73 for (int i = 0; i < priorityLevels; i++)
74 m_logicalThreadQueues[i] = new ConcurrentQueue<Func<LogicalThread>>();
67 }75 }
68 76
69 #endregion77 #endregion
@@ -86,7 +94,7 @@ namespace GSF.Threading · 16 unmodified lines
86 throw new ArgumentException("Max thread count must be greater than zero", nameof(value));94 throw new ArgumentException("Max thread count must be greater than zero", nameof(value));
87 95
88 int diff = value - m_maxThreadCount;96 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));
90 98
91 Interlocked.Exchange(ref m_maxThreadCount, value);99 Interlocked.Exchange(ref m_maxThreadCount, value);
92 100
@@ -111,6 +119,18 @@ namespace GSF.Threading · 18 unmodified lines
111 }119 }
112 }120 }
113 121
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
114 /// <summary>134 /// <summary>
115 /// Gets the current number of active physical threads.135 /// Gets the current number of active physical threads.
116 /// </summary>136 /// </summary>
@@ -133,18 +153,7 @@ namespace GSF.Threading · 16 unmodified lines
133 /// <returns>A new logical thread managed by this scheduler.</returns>153 /// <returns>A new logical thread managed by this scheduler.</returns>
134 public LogicalThread CreateThread()154 public LogicalThread CreateThread()
135 {155 {
136 return CreateThread(1);156 return new LogicalThread(this);
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);
148 }157 }
149 158
150 /// <summary>159 /// <summary>
@@ -152,11 +161,12 @@ namespace GSF.Threading · 1 unmodified line
152 /// thread has new actions to be processed.161 /// thread has new actions to be processed.
153 /// </summary>162 /// </summary>
154 /// <param name="thread">The thread with new actions to be processed.</param>163 /// <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)
156 {166 {
157 if (thread.TryActivate())167 if (thread.TryActivate(priority))
158 {168 {
159 m_logicalThreads.Enqueue(thread);169 Enqueue(thread);
160 ActivatePhysicalThread();170 ActivatePhysicalThread();
161 }171 }
162 }172 }
@@ -204,13 +214,19 @@ namespace GSF.Threading · 41 unmodified lines
204 private void ProcessLogicalThreads()214 private void ProcessLogicalThreads()
205 {215 {
206 Stopwatch stopwatch;216 Stopwatch stopwatch;
217 Func<LogicalThread> accessor = null;
207 LogicalThread thread;218 LogicalThread thread;
208 Action action;219 Action action;
209 220
210 stopwatch = new Stopwatch();221 stopwatch = new Stopwatch();
211 222
212 while (ThreadCount <= MaxThreadCount && m_logicalThreads.TryDequeue(out thread))223 while (ThreadCount <= MaxThreadCount && (object)m_logicalThreadQueues.FirstOrDefault(queue => queue.TryDequeue(out accessor)) != null)
213 {224 {
225 thread = accessor();
226
227 if ((object)thread == null)
228 continue;
229
214 action = thread.Pull();230 action = thread.Pull();
215 231
216 if ((object)action != null)232 if ((object)action != null)
@@ -226,17 +242,62 @@ namespace GSF.Threading · 9 unmodified lines
226 }242 }
227 243
228 if (thread.HasAction)244 if (thread.HasAction)
229 m_logicalThreads.Enqueue(thread);245 Enqueue(thread);
230 else246 else
231 thread.Deactivate();247 thread.Deactivate();
232 }248 }
233 249
234 DeactivatePhysicalThread();250 DeactivatePhysicalThread();
235 251
236 if (!m_logicalThreads.IsEmpty)252 if (!m_logicalThreadQueues.All(queue => queue.IsEmpty))
237 ActivatePhysicalThread();253 ActivatePhysicalThread();
238 }254 }
239 255
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
240 /// <summary>301 /// <summary>
241 /// Attempts to execute the given action.302 /// Attempts to execute the given action.
242 /// </summary>303 /// </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