ThreadPool 给出了惊人的结果,我这样做对吗? (不,我没有)

发布于 2024-07-26 23:19:56 字数 2099 浏览 2 评论 0原文

并不是说我不欣赏多线程或 ThreadPool 的强大功能,但我担心我会破坏某些东西,因为我的速度提高了大约 20 倍(比一分钟多缩短了 2-3 秒)相对简单地使用了ThreadPool。 因此,我在这里提交我的代码,以便被比我聪明得多的人撕碎。

我在这里做错了什么,还是这只是比我希望的更好的多线程候选者? (是的,这个函数是一个完整的线程:就像我说的,这过去需要一分钟多的时间才能运行)

编辑:要回答我自己的问题,不,这已经损坏了:它似乎正在运行多次,但在同一个触发器上。 这是因为 lambda 的处理方式吗?

private static void CompileEverything()
{
    try
    {
        // maintain the state of our systray icon
        object iconLock = new object();
        bool iconIsOut = true;

        // keep a count of how many threads are still running
        object runCountLock = new object();
        int threadRunning = 0;

        foreach (World w in Worlds)
        {
            foreach (Trigger t in w.Triggers)
            {
                lock (runCountLock)
                {
                    threadRunning++;
                }

                ThreadPool.QueueUserWorkItem(o =>
                {
                    // [snip]: Do some work involving compiling code already in memory with CSharpCodeProvider

                    // provide some pretty feedback
                    lock (iconLock)
                    {
                        if (iconIsOut)
                            notifyIcon.Icon = Properties.Resources.Icon16in;
                        else
                            notifyIcon.Icon = Properties.Resources.Icon16out;

                        iconIsOut = !iconIsOut;
                    }

                    lock (runCountLock)
                    {
                        threadRunning--;
                    }
                });
            }
        }

        // wait for all the threads to finish up
        while (true)
        {
            lock (runCountLock)
            {
                if (threadRunning == 0)
                    break;
            }
        }

        // set the notification icon to our default icon.
        notifyIcon.Icon = Properties.Resources.Icon16;
    }
    // we're going down before we finished starting...
    // oh well, be nice about it.
    catch (ThreadAbortException) { }
}

Not that I'm not appreciative of the powers of multithreading or ThreadPool, but I'm scared I broke something since I'm getting a roughly 20x speed increase (2-3s down from over a minute) with a relatively naive usage of ThreadPool. So I submit my code here to be torn apart by people far wiser than I.

Am I doing something wrong here, or is this just a far better candidate for multithreading than I ever hoped? (Yes, this function is an entire thread: like I said, this used to take over a minute to run)

EDIT: To answer my own question, no, this is broken: It seems to be running multiple times, but over the same trigger. Is this because of the way lambda are handled?

private static void CompileEverything()
{
    try
    {
        // maintain the state of our systray icon
        object iconLock = new object();
        bool iconIsOut = true;

        // keep a count of how many threads are still running
        object runCountLock = new object();
        int threadRunning = 0;

        foreach (World w in Worlds)
        {
            foreach (Trigger t in w.Triggers)
            {
                lock (runCountLock)
                {
                    threadRunning++;
                }

                ThreadPool.QueueUserWorkItem(o =>
                {
                    // [snip]: Do some work involving compiling code already in memory with CSharpCodeProvider

                    // provide some pretty feedback
                    lock (iconLock)
                    {
                        if (iconIsOut)
                            notifyIcon.Icon = Properties.Resources.Icon16in;
                        else
                            notifyIcon.Icon = Properties.Resources.Icon16out;

                        iconIsOut = !iconIsOut;
                    }

                    lock (runCountLock)
                    {
                        threadRunning--;
                    }
                });
            }
        }

        // wait for all the threads to finish up
        while (true)
        {
            lock (runCountLock)
            {
                if (threadRunning == 0)
                    break;
            }
        }

        // set the notification icon to our default icon.
        notifyIcon.Icon = Properties.Resources.Icon16;
    }
    // we're going down before we finished starting...
    // oh well, be nice about it.
    catch (ThreadAbortException) { }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

够运 2024-08-02 23:19:56

Interlocked.Increment 比锁定更好,但最后的轮询循环让我害怕。 首先,如果要循环,则每次执行 Thread.Sleep(0) 来释放处理器。 其次,如果你要轮询一个变量,那么你需要确保它要么被标记为易失性,要么使用 MemoryBarrier,否则编译器可能会假设没有外部线程会更改它,因此优化掉检查,从而导致无限环形。

更好的是每个线程检查它是否达到零,如果达到零则设置一个事件。 然后您可以等待事件而不是轮询。 唯一的技巧是您希望在调度循环之前在主线程中递增一次,然后在等待事件之前递减并检查零。

编辑

如果它因为重用触发器而被破坏,那么闭包就是错误的。 尝试将 world 的值复制到循环内部的本地变量中,并将该变量用于 lambda 表达式。

Interlocked.Increment is better than locking, but the polling loop at the end scares me. First, if you're going to loop, then do a Thread.Sleep(0) to release the processor each time. Second, if you're going to poll for a variable, then you need to make sure it's either marked volatile or you use MemoryBarrier, else the compiler may assume no outside thread will change it and therefore optimize away the check, leading to an infinite loop.

Even better would be for each thread to check for it hitting zero and set an event if it does. You can then wait on the event instead of polling. The only trick is that you want to increment once in the main thread before the dispatch loop, then decrement and check for zero before waiting on the event.

edit

If it's broken because it's reusing the trigger, then the closure is wrong. Trying copying the value of world into a variable local to the inside of the loop and using that variable for the lambda expression.

慵挽 2024-08-02 23:19:56

我认为你可以做得更好。 无需锁定 threadRunning 的更改。 您可以只使用 Interlocked.Increment() 和 Interlocked.Decrement():

        Interlocked.Increment(ref threadRunning);
        ThreadPool.QueueUserWorkItem(o =>
        {
            // [snip]: Do some work involving compiling code already in memory with CSharpCodeProvider

            // provide some pretty feedback
            lock (iconLock)
            {
                notifyIcon.Icon = (iconIsOut ? Properties.Resources.Icon16in : Properties.Resources.Icon16out);
                iconIsOut = !iconIsOut;
            }

            Interlocked.Decrement(ref threadRunning);
        });

I think you can do better. There is no need to lock around the changes to threadRunning. You can just use Interlocked.Increment() and Interlocked.Decrement():

        Interlocked.Increment(ref threadRunning);
        ThreadPool.QueueUserWorkItem(o =>
        {
            // [snip]: Do some work involving compiling code already in memory with CSharpCodeProvider

            // provide some pretty feedback
            lock (iconLock)
            {
                notifyIcon.Icon = (iconIsOut ? Properties.Resources.Icon16in : Properties.Resources.Icon16out);
                iconIsOut = !iconIsOut;
            }

            Interlocked.Decrement(ref threadRunning);
        });
无妨# 2024-08-02 23:19:56

线程池自动将运行线程的数量限制为最大效率的处理器数量。 每个上下文切换高达 1Mb(默认)以 4Kb 页面增量内存交换,因此,如果您使用的线程多于内核,则可能会得到无需上下文切换即可实现大量速度。

Well the ThreadPool automatically limits the number of running threads to the number of processors which is maximally efficient. Each context switch is up to 1Mb (default) in 4Kb page increments of memory swapping, so if you're using a lot more threads than cores, you could be getting a ton of speed just from no context switches.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文