编辑:TL; DR ConcurrentStack.Push
在多线程环境中表现不佳,我对此无能为力。
在尝试编写"自定义ThreadPool
",被称为" ThreadFactory
"后,我开始偶然发现一些特殊问题。
即ConcurrentStack.Push
似乎并不总是将传递的参数推送到堆栈。
ThreadFactory.cs
using System.Collections.Concurrent;
namespace CustomThreadFactory
{
public class ThreadFactory
{
public uint Working
{
get
{
return (uint)( _numWorkers - _available.Count ); // Returns how many workers are currently busy
}
set { }
}
private uint _numWorkers; // Stores the amount of workers
private ConcurrentStack<ThreadWorker> _available; // Stores the available workers
private ConcurrentQueue<ThreadTask> _backlog; // ThreadTask backlog, ThreadTasks queue up here if there're no workers available
public ThreadFactory( uint numWorkers = 8 )
{
_numWorkers = numWorkers;
_available = new ConcurrentStack<ThreadWorker>();
_backlog = new ConcurrentQueue<ThreadTask>();
for ( uint i = 0; i < numWorkers; i++ )
_available.Push( new ThreadWorker( this ) ); // Populate worker stack
}
public bool Continue( ThreadWorker sender, ref ThreadTask task )
{ // This function is called by ThreadWorker to decide whether it should lock next call
if( _backlog.Count > 0 ) // More work available = Do not lock, a new ThreadTask has been set
{
while ( !_backlog.TryDequeue( out task ) ) ; // Busy-wait TryDequeue on the backlog queue, this should only take a couple cycles (if not one)
return false; // ThreadWorker._lockThread boolean is set to false here, it won't lock next cycle
}
else
{
task = null; // No more tasks available, last task is marked for GC
_available.Push( sender ); // Push the worker back onto the available stack
return true; // ThreadWorker._lockThread boolean is set to true here, the worker thread will now lock and wait
}
}
public void Enqueue( ThreadTask task )
{
if( _available.Count > 0 ) // If there's a worker available (and sleeping/locked), wake them and pass the new task
{
ThreadWorker worker;
while ( !_available.TryPop( out worker ) ) ; // Busy-wait TryPop on worker stack, this should only take a couple cycles (if not one)
worker.Wake( task );
}
else // Otherwise this task will be added to the backlog queue
{
_backlog.Enqueue( task );
}
}
}
}
ThreadWorker.cs
using System.Threading;
namespace CustomThreadFactory
{
public class ThreadWorker
{
private ThreadFactory _factory; // Parent ThreadFactory
private volatile bool _lockThread; // Should lock on next cycle?
private object _threadLock; // Locking object
private Thread _thread; // Worker thread
private ThreadTask _task; // Current task
public ThreadWorker( ThreadFactory parent )
{
_factory = parent;
_lockThread = true;
_threadLock = new object();
_thread = new Thread( _Work );
_thread.Start();
}
~ThreadWorker()
{
_thread?.Abort(); // If _thread is "valid", _thread.Abort
_threadLock = null; // Mark for GC (not sure if required)
}
private void _Work()
{
while( true )
{
if ( _lockThread )
lock ( _threadLock )
Monitor.Wait( _threadLock );
// ---
_task?.Run(); // If _task is "valid", _task.Run
// If _factory has more tasks, _task will be set and _lockThread will be false
// Otherwise the worker thread will lock on the next cycle
_lockThread = _factory.Continue( this, ref _task );
}
}
public void Wake( ThreadTask task )
{
_task = task; // Set _task
_lockThread = false; // Don't lock the thread unless the ThreadFactory specifically "says so"
lock ( _threadLock )
Monitor.Pulse( _threadLock ); // Unlock if currently locked
}
}
}
ThreadTask.cs
namespace CustomThreadFactory
{
public delegate void ThreadAction( object parameters );
public class ThreadTask
{
private ThreadAction _action; // Delegate action
private object _parameters; // Object parameter(s)
public ThreadTask( ThreadAction action ) // Parameterless constructor
{
_action = action;
_parameters = null;
}
public ThreadTask( ThreadAction action, object parameters )
{
_action = action;
_parameters = parameters;
}
public void Run() // Executes the delegate, passing the stored parameters (or null)
{
_action( _parameters );
}
}
}
Program.cs
namespace CustomThreadFactory
{
class Program
{
static void Main( string[] args )
{
ThreadFactory f = new ThreadFactory();
Stopwatch s = new Stopwatch();
s.Start();
for ( uint i = 0; i < 512; i++ )
{
f.Enqueue( new ThreadTask( ( object parameters ) =>
{
for ( uint x = 0; x < 2048; x++ )
{
long y = (long)Math.Pow( x, 2 );
}
} ) );
}
while ( f.Working > 0 )
Thread.Sleep( 1 );
s.Stop();
Console.WriteLine( string.Format( "Took {0}ms", s.Elapsed.TotalMilliseconds - 1 ) );
Console.ReadLine();
}
}
}
问题似乎在于ThreadFactory.Continue
,如果未达到_backlog.Count > 0
,ThreadWorker sender
应该重新添加到可用工人堆叠
虽然这并不总是发生。 (暗示有时它重新成功添加所有ThreadWorker
我尝试过:
ThreadWorker.Continue
中的Console.WriteLine(&#34;线程现在可用&#34;),始终打印正确的行数(默认为8),但并非所有ThreadWorker
都被推送到可用工人堆栈 lock( _stackLock )
_available.Push( sender )
左右(无差异)从现在开始,我无法真正看到导致ConcurrentStack
不将ThreadWorker
重新推送到可用工作人员堆栈的原因。
非常感谢你的帮助。
答案 0 :(得分:1)
您有一些竞争条件,Monitor.Lock
/ Monitor.Pulse
出现问题。我更改了一些代码以使其基本正常工作;
ThreadFactory
public class ThreadFactory
{
...
public ThreadTask Continue( ThreadWorker sender )
{
if ( _backlog.Count > 0 )
{
ThreadTask task;
do
{
if ( _backlog.TryDequeue( out task ) )
{
return task;
}
}
while ( _backlog.Count > 0 );
}
_available.Push( sender );
return null;
}
public void Enqueue( ThreadTask task )
{
if ( _available.Count > 0 ) // If there's a worker available (and sleeping/locked), wake them and pass the new task
{
ThreadWorker worker;
do
{
if ( _available.TryPop( out worker ) )
{
worker.Wake( task );
}
}
while ( _available.Count > 0 ); // Busy-wait TryPop on worker stack, this should only take a couple cycles (if not one)
}
else // Otherwise this task will be added to the backlog queue
{
_backlog.Enqueue( task );
}
}
}
以及整个ThreadWorker
public class ThreadWorker
{
private ThreadFactory _factory; // Parent ThreadFactory
private AutoResetEvent _event = new AutoResetEvent( false );
private Thread _thread; // Worker thread
private volatile ThreadTask _task; // Current task
public ThreadWorker( ThreadFactory parent, string name = null )
{
Name = name;
_factory = parent;
_thread = new Thread( _Work );
_thread.Start();
}
public string Name { get; }
~ThreadWorker()
{
_thread?.Abort(); // If _thread is "valid", _thread.Abort
}
private void _Work()
{
while ( true )
{
_event.WaitOne();
_task?.Run(); // If _task is "valid", _task.Run
// If _factory has more tasks, _task will be set and _lockThread will be false
// Otherwise the worker thread will lock on the next cycle
Wake( _factory.Continue( this ) );
}
}
public void Wake( ThreadTask task )
{
_task = task;
if ( _task != null )
{
_event.Set();
}
}
}