减慢创建具有许多线程的对象

xan*_*tos 9 .net c# performance multithreading asynchronous

我正在做一个产生数百个线程的项目.所有这些线程都处于"休眠"状态(它们被锁定在Monitor对象上).我注意到,如果我增加"休眠"线程的数量,程序会非常慢."有趣"的是,看着任务管理器似乎线程数越多,处理器就越自由.我已将问题缩小到对象创建.

有人可以向我解释一下吗?

我制作了一个小样本来测试它.这是一个控制台程序.它为每个处理器创建一个线程,并通过简单的测试("新对象()")测量它的速度.不,"新对象()"没有被淘汰(如果你不信任我,试试).主线程显示每个线程的速度.按CTRL-C,该程序产生50个"睡眠"线程.减速开始于50个线程.在任务管理器上可以看到大约250个,CPU不是100%使用(在我的是82%).

我已经尝试了三种锁定"休眠"线程的方法:Thread.CurrentThread.Suspend()(坏,坏,我知道:-)),锁定已经锁定的对象和Thread.Sleep(Timeout.Infinite).一样的.如果我使用新的Object()注释该行,并将其替换为Math.Sqrt(或没有任何内容),则问题不存在.速度不随线程数而变化.别人可以查一下吗?有谁知道瓶颈在哪里?

啊......你应该在发布模式下测试它而不从Visual Studio中启动它.我在双处理器上使用XP sp3(没有HT).我用.NET 3.5和4.0测试过它(测试不同的框架运行时)

namespace TestSpeed
{
    using System;
    using System.Collections.Generic;
    using System.Threading;

    class Program
    {
        private const long ticksInSec = 10000000;
        private const long ticksInMs = ticksInSec / 1000;
        private const int threadsTime = 50;
        private const int stackSizeBytes = 256 * 1024;
        private const int waitTimeMs = 1000;

        private static List<int> collects = new List<int>();
        private static int[] objsCreated;

        static void Main(string[] args)
        {
            objsCreated = new int[Environment.ProcessorCount];
            Monitor.Enter(objsCreated);

            for (int i = 0; i < objsCreated.Length; i++)
            {
                new Thread(Worker).Start(i);
            }

            int[] oldCount = new int[objsCreated.Length];

            DateTime last = DateTime.UtcNow;

            Console.Clear();

            int numThreads = 0;
            Console.WriteLine("Press Ctrl-C to generate {0} sleeping threads, Ctrl-Break to end.", threadsTime);

            Console.CancelKeyPress += (sender, e) =>
            {
                if (e.SpecialKey != ConsoleSpecialKey.ControlC)
                {
                    return;
                }

                for (int i = 0; i < threadsTime; i++)
                {
                    new Thread(() =>
                    {
                        /* The same for all the three "ways" to lock forever a thread */
                        //Thread.CurrentThread.Suspend();
                        //Thread.Sleep(Timeout.Infinite);
                        lock (objsCreated) { }
                    }, stackSizeBytes).Start();

                    Interlocked.Increment(ref numThreads);
                }

                e.Cancel = true;
            };

            while (true)
            {
                Thread.Sleep(waitTimeMs);

                Console.SetCursorPosition(0, 1);

                DateTime now = DateTime.UtcNow;

                long ticks = (now - last).Ticks;

                Console.WriteLine("Slept for {0}ms", ticks / ticksInMs);

                Thread.MemoryBarrier();

                for (int i = 0; i < objsCreated.Length; i++)
                {
                    int count = objsCreated[i];
                    Console.WriteLine("{0} [{1} Threads]: {2}/sec    ", i, numThreads, ((long)(count - oldCount[i])) * ticksInSec / ticks);
                    oldCount[i] = count;
                }

                Console.WriteLine();

                CheckCollects();

                last = now;
            }
        }

        private static void Worker(object obj)
        {
            int ix = (int)obj;

            while (true)
            {
                /* First and second are slowed by threads, third, fourth, fifth and "nothing" aren't*/

                new Object();
                //if (new Object().Equals(null)) return;
                //Math.Sqrt(objsCreated[ix]);
                //if (Math.Sqrt(objsCreated[ix]) < 0) return;
                //Interlocked.Add(ref objsCreated[ix], 0);

                Interlocked.Increment(ref objsCreated[ix]);
            }
        }

        private static void CheckCollects()
        {
            int newMax = GC.MaxGeneration;

            while (newMax > collects.Count)
            {
                collects.Add(0);
            }

            for (int i = 0; i < collects.Count; i++)
            {
                int newCol = GC.CollectionCount(i);

                if (newCol != collects[i])
                {
                    collects[i] = newCol;
                    Console.WriteLine("Collect gen {0}: {1}", i, newCol);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 10

启动Taskmgr.exe,进程选项卡.查看+选择列,勾选"Page Fault Delta".您将看到分配数百兆字节的影响,只是为了存储您创建的所有这些线程的堆栈.每当该进程的数字闪烁时,程序就会阻塞操作系统将数据从磁盘分页到RAM.

TANSTAAFL,没有免费午餐这样的东西.

  • @Chris,没有本机模式堆栈,一个堆栈同时服务.但是,创建的每个线程也具有24 KB内核模式堆栈. (2认同)

Jon*_*eet 5

我的猜测是问题是垃圾收集需要线程之间的一定程度的合作 - 要么需要检查它们是否全部被暂停,要么让它们暂停并等待它发生等等.(即使他们停职了,它必须告诉他们不要醒来!)

当然,这描述了一个"停止世界"的垃圾收集器.我相信至少有两三种不同的GC实现在并行性方面的细节上有所不同......但我怀疑所有这些实现在获得线程合作方面都有一些工作要做.