我已经创建了一个自定义线程池实用程序,但似乎有一个我找不到的问题。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace iWallpaper.S3Uploader
{
public class QueueManager<T>
{
private readonly Queue queue = Queue.Synchronized(new Queue());
private readonly AutoResetEvent res = new AutoResetEvent(true);
private readonly AutoResetEvent res_thr = new AutoResetEvent(true);
private readonly Semaphore sem = new Semaphore(1, 4);
private readonly Thread thread;
private Action<T> DoWork;
private int Num_Of_Threads;
private QueueManager()
{
Num_Of_Threads = 0;
maxThread = 5;
thread = new Thread(Worker) {Name = "S3Uploader EventRegisterer"};
thread.Start();
// log.Info(String.Format("{0} [QUEUE] FileUploadQueueManager created", DateTime.Now.ToLongTimeString()));
}
public int maxThread { get; set; }
public static FileUploadQueueManager<T> Instance
{
get { return Nested.instance; }
}
/// <summary>
/// Executes multythreaded operation under items
/// </summary>
/// <param name="list">List of items to proceed</param>
/// <param name="action">Action under item</param>
/// <param name="MaxThreads">Maximum threads</param>
public void Execute(IEnumerable<T> list, Action<T> action, int MaxThreads)
{
maxThread = MaxThreads;
DoWork = action;
foreach (T item in list)
{
Add(item);
}
}
public void ExecuteNoThread(IEnumerable<T> list, Action<T> action)
{
ExecuteNoThread(list, action, 0);
}
public void ExecuteNoThread(IEnumerable<T> list, Action<T> action, int MaxThreads)
{
foreach (T wallpaper in list)
{
action(wallpaper);
}
}
/// <summary>
/// Default 10 threads
/// </summary>
/// <param name="list"></param>
/// <param name="action"></param>
public void Execute(IEnumerable<T> list, Action<T> action)
{
Execute(list, action, 10);
}
private void Add(T item)
{
lock (queue)
{
queue.Enqueue(item);
}
res.Set();
}
private void Worker()
{
while (true)
{
if (queue.Count == 0)
{
res.WaitOne();
}
if (Num_Of_Threads < maxThread)
{
var t = new Thread(Proceed);
t.Start();
}
else
{
res_thr.WaitOne();
}
}
}
private void Proceed()
{
Interlocked.Increment(ref Num_Of_Threads);
if (queue.Count > 0)
{
var item = (T) queue.Dequeue();
sem.WaitOne();
ProceedItem(item);
sem.Release();
}
res_thr.Set();
Interlocked.Decrement(ref Num_Of_Threads);
}
private void ProceedItem(T activity)
{
if (DoWork != null)
DoWork(activity);
lock (Instance)
{
Console.Title = string.Format("ThrId:{0}/{4}, {1}, Activity({2} left):{3}",
thread.ManagedThreadId, DateTime.Now, queue.Count, activity,
Num_Of_Threads);
}
}
#region Nested type: Nested
protected class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
internal static readonly QueueManager<T> instance = new FileUploadQueueManager<T>();
}
#endregion
}
}
问题出在这里:
Console.Title = string.Format("ThrId:{0}/{4}, {1}, Activity({2} left):{3}",
thread.ManagedThreadId, DateTime.Now, queue.Count, activity,
Num_Of_Threads);
标题中总是有一个线程ID。程序似乎在一个线程中工作。
样本用法:
var i_list = new int[] {1, 2, 4, 5, 6, 7, 8, 6};
QueueManager<int>.Instance.Execute(i_list,
i =>
{
Console.WriteLine("Some action under element number {0}", i);
}, 5);
P.S。:它非常混乱,但我还在努力。
答案 0 :(得分:5)
我查看了你的代码,这里有几个问题。
Proceed方法不是线程安全的。这两行是问题
if (queue.Count > 0) { var item = (T)queue.Dequeue(); ... }
使用同步队列仅保证单个访问是安全的。因此.Count和.Dequeue方法都不会混淆队列的内部结构。但是想象一下两个线程同时使用count 1队列运行这些代码行的情况
Worker和Proceed之间存在竞争条件,可能导致死锁。应该切换以下两行代码。
代码:
res_thr.Set() Interlocked.Decrement(ref Num_Of_Threads);
第一行将取消阻止Worker方法。如果它运行得足够快,它将返回到外观,注意Num_Of_Threads&lt; maxThreads并返回res_thr.WaitOne()。如果当前没有其他线程正在运行,那么这将导致代码中出现死锁。使用较少的最大线程数(例如1)非常容易命中。反转这两行代码应该可以解决问题。
答案 1 :(得分:4)
编写健壮的线程代码并非易事。您可能会看到许多线程池以供参考,但另请注意,Parallel Extensions(可用作CTP或更高版本的.NET 4.0)包含许多其他线程构造。框(在TPL / CCR中)。例如,Parallel.For
/ Parallel.ForEach
,它处理工作窃取,并有效地处理可用的核心。
有关预卷线程池的示例,请参阅Jon Skeet的CustomThreadPool
here。
答案 2 :(得分:2)
我认为你可以做很多事情。
这是我使用的线程池的修改后的表单(我没有测试修改):
唯一的同步。你需要的原始是一个监视器,锁定在线程池上。您不需要信号量或重置事件。
internal class ThreadPool
{
private readonly Thread[] m_threads;
private readonly Queue<Action> m_queue;
private bool m_shutdown;
private object m_lockObj;
public ThreadPool(int numberOfThreads)
{
Util.Assume(numberOfThreads > 0, "Invalid thread count!");
m_queue = new Queue<Action>();
m_threads = new Thread[numberOfThreads];
m_lockObj = new object();
lock (m_lockObj)
{
for (int i = 0; i < numberOfWriteThreads; ++i)
{
m_threads[i] = new Thread(ThreadLoop);
m_threads[i].Start();
}
}
}
public void Shutdown()
{
lock (m_lockObj)
{
m_shutdown = true;
Monitor.PulseAll(m_lockObj);
if (OnShuttingDown != null)
{
OnShuttingDown();
}
}
foreach (var thread in m_threads)
{
thread.Join();
}
}
public void Enqueue(Action a)
{
lock (m_lockObj)
{
m_queue.Enqueue(a);
Monitor.Pulse(m_lockObj);
}
}
private void ThreadLoop()
{
Monitor.Enter(m_lockObj);
while (!m_shutdown)
{
if (m_queue.Count == 0)
{
Monitor.Wait(m_lockObj);
}
else
{
var a = m_queue.Dequeue();
Monitor.Pulse(m_lockObj);
Monitor.Exit(m_lockObj);
try
{
a();
}
catch (Exception ex)
{
Console.WriteLine("An unhandled exception occured!\n:{0}", ex.Message, null);
}
Monitor.Enter(m_lockObj);
}
}
Monitor.Exit(m_lockObj);
}
}
答案 3 :(得分:1)
您应该使用内置线程池。在运行你的代码时,我注意到你的一系列线程,但是由于队列计数<1,你只是退出,这一直持续到队列实际填充然后你的下一个线程处理所有内容。这是一个非常昂贵的过程。如果你有事可做,你应该只旋转线程。