QueueUserWorkItem需要多长时间才能执行?

时间:2014-07-14 14:53:22

标签: c# multithreading threadpool

我正在开发的应用程序处理数据时,来自阻塞集合的消费者线程中的数据处理数据(从不超过1毫秒)它将处理后的数据从ThreadPool旋转到一个新线程,然后发送给我的用户并存储在数据库中。

我在调用ThreadPool.QueueUserWorkItem之前启动一个秒表,并将其作为我在ThreadPool.QueueUserWorkItem调用的函数中做的第一件事之一停止(在此之前任何代码的时钟都不到1毫秒)。

这个秒表报告的时间让我有点担心,平均时间是4毫秒(没有问题)但是最大值超过900毫秒,最少使用的线程是1,最常用的是~60和平均值是~40。

虽然只要平均值保持不变就不应该成为一个问题,但我很想知道为什么偶尔会有〜1秒等待线程,而我通常有900+免费。

一些示例代码将独立运行,其中sleep代替实际处理:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static int[] AvailableThreads;
        static Stopwatch[] stopwatch;
        static BlockingCollection<int> Queue;
        static ManualResetEvent consumerEvent;
        static ManualResetEvent alldone;
        static bool disposed = false;

        static void Main(string[] args)
        {
            int iterations = 10000;
            stopwatch = new Stopwatch[iterations];
            AvailableThreads = new int[iterations];
            Queue = new BlockingCollection<int>();
            consumerEvent = new ManualResetEvent(false);
            alldone = new ManualResetEvent(false);

            Thread processThread = new Thread(new ThreadStart(ProcessThread));
            processThread.IsBackground = true;
            processThread.Start();

            int MaxThreads = 0;
            int y = 0;

            ThreadPool.GetMaxThreads(out MaxThreads, out y);

            for (int i = 0; i < stopwatch.Length; i++)
            {
                Queue.Add(i);
                consumerEvent.Set();
            }

            alldone.Reset();
            alldone.WaitOne();

            long av = 0;
            long max = 0;

            int threadsAv = 0;
            int threadsMax = 0;

            for (int i = 0; i < stopwatch.Length; i++)
            {
                long ms = stopwatch[i].ElapsedMilliseconds;
                av += ms;
                threadsAv += AvailableThreads[i];
                if (max < ms) max = ms;
                if (threadsMax < AvailableThreads[i]) threadsMax = AvailableThreads[i];
            }

            if(av != 0) av = av / stopwatch.Length;

            if (threadsAv != 0) threadsAv = threadsAv / stopwatch.Length;

            Console.WriteLine("Average Time: {0}, Max Time: {1}, Max Thread: {2}, Average Available Threads: {3}, Max Available Threads: {4}", av, max, MaxThreads, threadsAv, threadsMax);

            Console.ReadLine();

            disposed = true;
        }

        static void ProcessThread()
        {
            while (!disposed)
            {
                foreach (int i in Queue.GetConsumingEnumerable())
                {
                    if (disposed) return;
                    // Proccess a bit of data here .....
                    stopwatch[i] = new Stopwatch();
                    stopwatch[i].Start();
                    int y = 0;
                    ThreadPool.GetAvailableThreads(out AvailableThreads[i], out y);
                    ThreadPool.QueueUserWorkItem(TransmitThread, i);
                }

                consumerEvent.Reset();
                consumerEvent.WaitOne();
            }
        }

        static void TransmitThread(object data)
        {
            int i = (int)data;
            stopwatch[i].Stop();
            //Fake some work.

            Thread.Sleep(1);

            if (i == stopwatch.Length - 1) alldone.Set();
        }
    }
}

示例输出:

Average Time: 581, Max Time: 1126, Max Thread: 1023, Average Available Threads: 1015, Max Available Threads: 1023

任何人都可以对此提供一些见解吗?

2 个答案:

答案 0 :(得分:3)

  

我可以确认问题是由于引起的节流   超过线程池上的最小值,增加此数字   显着提高了性能。

您可以增加将立即创建的最小线程数。这样做的唯一不利后果是,如果您的工作负载长时间不需要这些线程,它可能会导致更高的内存使用量。 这是解决您问题的有效方法。

另一方面:为什么你有数百甚至数千(?)个线程在运行?!这肯定是支持的,并不是不可靠的。但它非常不寻常,暗示了建筑问题。考虑使用异步IO和异步等待。您不必使所有内容异步。只是大部分时间阻塞的地方。可能有少数地方导致99%的阻塞。使这些异步并且您的线程数降至正常水平。

答案 1 :(得分:1)

ThreadPool的重点是以立即运行工作项。关键是要运行最佳个线程数。默认情况下,这是处理器可用的执行核心数。如果您运行更多活动线程而不是核心,那么您将完成 less 工作。操作系统被迫将活动线程上下文切换到核心上,这会减少专用于执行代码的机器周期总数。

所以看到900毫秒的延迟并不值得注意。您当时只有太多活动的QUWI工作项处于活动状态。你添加的那个最终会得到服务。

这通常是一个消防问题的迹象。你要求机器做的比它能做的更多。这非常正常,ThreadPool通过延迟工作来避免麻烦,因此这些线程不能使用太多资源并使用例如OutOfMemoryException来破坏程序。

看到40到60个线程处于活动状态是ThreadPool认为工作项被卡住时使用的一种解决方法。每0.5秒,它允许额外的线程开始弥补。查看工作项目是否依赖于非生产性工作的最简单方法是查看任务管理器中的处理器利用率。如果它不是100%那么线程阻塞太多。这样的线程不是线程池的好选择,它们可以通过线程或长时间运行的任务更好地服务。更改默认的最小线程数是一种解决方法,但是相当粗糙。你有点害怕不得不对此感到恐慌,因为你的代码是假的,所以很难说。