【问题标题】:Order of thread activation in a c# program on a 4 cores PC4核PC上的c#程序中的线程激活顺序
【发布时间】:2017-12-08 10:04:16
【问题描述】:

我对以下 C# 代码的线程激活顺序感到困惑。它创建10个线程,随机启动它们,每个线程模拟执行一个耗时的工作10次,如果你检查调试输出,线程似乎不是随机提取的,请看下面的输出示例,注意线程#3 ,#5,#6总是捡起来,当#3 #5 #6完成后,#10 #2 #8总是捡起来,等等……(我知道设计不好,请关注现象)

我的电脑有 i7-7820HQ cpu,有 4 个核心,运行 windows 10。

有人可以解释为什么这些线程不是随机挑选的,而且它们似乎以某种方式分组。

非常感谢!

---- 调试输出----

线程 #10 获得了锁并且正在为任务 #0 工作 线程 #5 获得了锁并正在为任务 #0 工作 线程#3 获得了锁并且正在为任务#0 工作 线程#6 获得了锁并且正在为任务#0 工作 线程 #5 获得了锁并正在为任务 #1 工作 线程#3 获得了锁并且正在为任务#1 工作 线程 #6 获得了锁并正在为任务 #1 工作 ... 线程 #5 获得了锁并正在为任务 #9 工作 线程#3 获得了锁并且正在为任务#9 工作 线程#6 获得了锁并且正在为任务#9 工作 ... 线程#8 获得了锁并且正在为任务#0 工作 线程 #2 获得了锁并正在为任务 #0 工作 线程 #8 获得了锁并正在为任务 #1 工作 线程 #2 获得了锁并正在为任务 #1 工作 线程 #10 获得了锁并正在为任务 #1 工作 ... 线程 #8 获得了锁并正在为任务 #9 工作 线程 #2 获得了锁并正在为任务 #9 工作 线程 #10 获得了锁并正在为任务 #9 工作 ...
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static Hashtable _sharedBetweenThreads = new Hashtable();

        static void Main(string[] args)
        {
            Random random = new Random(DateTime.Now.Second);
            var startOrders = new int[10];
            for (int i = 0; i < startOrders.Length; i++)
            {
                startOrders[i] = i;
            }

            //shuffle the array
            for (int i = startOrders.Length - 1; i >= 0; i--)
            {
                int j = random.Next(0, i);
                int temp = startOrders[i];
                startOrders[i] = startOrders[j];
                startOrders[j] = temp;
            }


            Thread[] threads = new Thread[startOrders.Length];
            for(int i = 0; i < startOrders.Length; i++)
            {
                threads[i] = new Thread(new ThreadStart(ThreadProc));
            }


            for (int i = 0; i < startOrders.Length; i++)
            {
                threads[startOrders[i]].Start();
            }

            Console.ReadLine();
        }

        static void ThreadProc()
        {
            // simulates there are 10 tasks needs to do.
            for (int i = 0; i < 10; i++)
            {
                lock (_sharedBetweenThreads.SyncRoot)
                {
                    Debug.Print(string.Format("Thread #{0} acquired the lock and is working for task #{1}", Thread.CurrentThread.ManagedThreadId, i));

                    // simulates a work.
                    Thread.Sleep(500);
                }

            }
        }
    }
}

The screenshot of debug output

--- 附加信息 ---

  • 该程序根本不是并行程序,因为 10 个线程之间共享锁。
  • 如果循环计数从 10 变为无限,则始终激活 3 个线程,其余 7 个线程无法获得激活机会,就像永远“死锁”一样。

【问题讨论】:

  • 尝试在循环之前将Random rng = new Random(); 添加到ThreadProc(),然后在循环中执行Thread.Sleep(400 + rng.Next(200));,看看是否会得到不同的结果。也许发生了某种锁定步骤。
  • 没有订单。实际上,操作系统有意避免意外排序,这是针对锁车队的对策。后台is here.
  • 最令人困惑的是,有10个线程,只能激活3个线程,在循环结束之前,线程调度器总是选择这3个线程,其余7个一直在等待。不应该每个线程都有相同的机会被激活吗?

标签: c# .net multithreading locking


【解决方案1】:

我猜您的困惑源于您正在使用 .NET 线程池这一事实 - 例如一个托管的线程池 - 但您希望在系统级别处理线程,这并不完全相同。内部线程池为您提供它认为合适的线程。这与问题无关,哪个内核在哪个时刻运行哪个线程。

【讨论】:

  • 是不是说这种调度方式就是.net托管线程池的工作方式?
  • 不,在我看来,对并行性和线程方法存在完全误解。由于您的锁定,在您的代码中根本没有并行性。每个线程都等待另一个线程完成。使用 Sleep,您无法模拟工作。
  • 感谢奥拉夫的回答。是的,就像你说的,代码本身根本不是并行的,因为所有 10 个线程之间共享一个锁,并且在一个时间片中只有一个线程可以运行,其余 9 个线程等待同一个锁。
  • 假设激活线程的id是N,我希望N可能是0-9随机的,每个线程应该有公平的百分比被激活,而不是上面描述的现象,只有3 (我觉得应该是4,和我的核心数一模一样。总数是1(主线程)+3(工作线程))线程总是可以激活的。如果我将循环计数从 10 更改为无限,其余 7 个线程将永远无法激活。
  • 内部调度程序试图以有效的方式完成他的工作。任务切换是有代价的。调度器决定何时切换,但他必须有一个公平的机会。用你的锁,你几乎不可能做到这一点。扔掉你的锁 - 你不需要它 - 并将睡眠时间减少到 50。然后你的输出就会是这样,我猜你已经预料到了。
【解决方案2】:

使用线程并不意味着您正在使用您拥有的 4 个内核。

你必须使用并行:

class Program
{
    private static object _lock = new Object();

    static void Main(string[] args)
    {
        Random random = new Random(DateTime.Now.Second);

        int[] startOrders = new int[10];
        for (int i = 0; i < startOrders.Length; i++)
        {
            startOrders[i] = i;
        }

        //shuffle the array
        for (int i = startOrders.Length - 1; i >= 0; i--)
        {
            int j = random.Next(0, i);
            int temp = startOrders[i];
            startOrders[i] = startOrders[j];
            startOrders[j] = temp;
        }


        Thread[] threads = new Thread[startOrders.Length];
        for (int i = 0; i < startOrders.Length; i++)
        {
            threads[i] = new Thread(ThreadProc)
                             {
                                 Name = $"#{startOrders[i]}"

                             };
        }


        Parallel.ForEach(threads, thread => thread.Start());

        Console.ReadLine();
    }

    static void ThreadProc()
    {
        // simulates there are 10 tasks needs to do.
        for (int i = 0; i < 10; i++)
        {
            lock (_lock)
            {
                Debug.Print(
                    $"Thread {Thread.CurrentThread.Name} acquired the lock and is working for task #{i}");

                // simulates a work.
                Thread.Sleep(5);
            }
        }
    }
}

输出

Thread #6 acquired the lock and is working for task #0
Thread #4 acquired the lock and is working for task #0
Thread #8 acquired the lock and is working for task #0
Thread #6 acquired the lock and is working for task #1
Thread #5 acquired the lock and is working for task #0
Thread #3 acquired the lock and is working for task #0
Thread #6 acquired the lock and is working for task #2
Thread #7 acquired the lock and is working for task #0
Thread #9 acquired the lock and is working for task #0
Thread #6 acquired the lock and is working for task #3
Thread #4 acquired the lock and is working for task #1
Thread #8 acquired the lock and is working for task #1
Thread #0 acquired the lock and is working for task #0
Thread #5 acquired the lock and is working for task #1
Thread #3 acquired the lock and is working for task #1
Thread #7 acquired the lock and is working for task #1
Thread #9 acquired the lock and is working for task #1
Thread #2 acquired the lock and is working for task #0
Thread #1 acquired the lock and is working for task #0
Thread #0 acquired the lock and is working for task #1
Thread #6 acquired the lock and is working for task #4
Thread #4 acquired the lock and is working for task #2
Thread #8 acquired the lock and is working for task #2
Thread #3 acquired the lock and is working for task #2
Thread #7 acquired the lock and is working for task #2
Thread #5 acquired the lock and is working for task #2
Thread #9 acquired the lock and is working for task #2
Thread #2 acquired the lock and is working for task #1
Thread #0 acquired the lock and is working for task #2
Thread #1 acquired the lock and is working for task #1
Thread #6 acquired the lock and is working for task #5
Thread #4 acquired the lock and is working for task #3
Thread #8 acquired the lock and is working for task #3
Thread #5 acquired the lock and is working for task #3
Thread #3 acquired the lock and is working for task #3
Thread #7 acquired the lock and is working for task #3
Thread #6 acquired the lock and is working for task #6
Thread #4 acquired the lock and is working for task #4
Thread #8 acquired the lock and is working for task #4
Thread #5 acquired the lock and is working for task #4
Thread #3 acquired the lock and is working for task #4
Thread #7 acquired the lock and is working for task #4
Thread #6 acquired the lock and is working for task #7
Thread #4 acquired the lock and is working for task #5
Thread #8 acquired the lock and is working for task #5
Thread #5 acquired the lock and is working for task #5
Thread #3 acquired the lock and is working for task #5
Thread #7 acquired the lock and is working for task #5
Thread #9 acquired the lock and is working for task #3
Thread #6 acquired the lock and is working for task #8
Thread #4 acquired the lock and is working for task #6
Thread #5 acquired the lock and is working for task #6
Thread #8 acquired the lock and is working for task #6
Thread #4 acquired the lock and is working for task #7
Thread #3 acquired the lock and is working for task #6
Thread #6 acquired the lock and is working for task #9
Thread #7 acquired the lock and is working for task #6
The thread 0xfa0 has exited with code 0 (0x0).
Thread #5 acquired the lock and is working for task #7
Thread #8 acquired the lock and is working for task #7
Thread #9 acquired the lock and is working for task #4
Thread #4 acquired the lock and is working for task #8
Thread #3 acquired the lock and is working for task #7
Thread #0 acquired the lock and is working for task #3
Thread #7 acquired the lock and is working for task #7
Thread #5 acquired the lock and is working for task #8
Thread #8 acquired the lock and is working for task #8
Thread #4 acquired the lock and is working for task #9
Thread #3 acquired the lock and is working for task #8
The thread 0x329c has exited with code 0 (0x0).
Thread #9 acquired the lock and is working for task #5
Thread #7 acquired the lock and is working for task #8
Thread #5 acquired the lock and is working for task #9
The thread 0x3fcc has exited with code 0 (0x0).
Thread #8 acquired the lock and is working for task #9
The thread 0xe2c has exited with code 0 (0x0).
Thread #0 acquired the lock and is working for task #4
Thread #3 acquired the lock and is working for task #9
The thread 0x22ec has exited with code 0 (0x0).
Thread #2 acquired the lock and is working for task #2
Thread #7 acquired the lock and is working for task #9
The thread 0x2bfc has exited with code 0 (0x0).
Thread #9 acquired the lock and is working for task #6
Thread #1 acquired the lock and is working for task #2
Thread #0 acquired the lock and is working for task #5
Thread #2 acquired the lock and is working for task #3
Thread #2 acquired the lock and is working for task #4
Thread #9 acquired the lock and is working for task #7
Thread #2 acquired the lock and is working for task #5
Thread #1 acquired the lock and is working for task #3
Thread #0 acquired the lock and is working for task #6
Thread #0 acquired the lock and is working for task #7
Thread #9 acquired the lock and is working for task #8
Thread #2 acquired the lock and is working for task #6
Thread #1 acquired the lock and is working for task #4
Thread #1 acquired the lock and is working for task #5
Thread #0 acquired the lock and is working for task #8
Thread #9 acquired the lock and is working for task #9
The thread 0x1f44 has exited with code 0 (0x0).
Thread #1 acquired the lock and is working for task #6
Thread #2 acquired the lock and is working for task #7
Thread #2 acquired the lock and is working for task #8
Thread #0 acquired the lock and is working for task #9
The thread 0x1fc8 has exited with code 0 (0x0).
Thread #1 acquired the lock and is working for task #7
Thread #1 acquired the lock and is working for task #8
Thread #1 acquired the lock and is working for task #9
The thread 0x2ab0 has exited with code 0 (0x0).
Thread #2 acquired the lock and is working for task #9
The thread 0x2944 has exited with code 0 (0x0).
The thread 0x322c has exited with code 0 (0x0).
The thread 0x146c has exited with code 0 (0x0).
The thread 0x3dcc has exited with code 0 (0x0).
The thread 0x3928 has exited with code 0 (0x0).

我不确定,但如果我使用 Thread.Name,则 Thread.CurrentThread.ManagedThreadId 似乎可以按您的意愿工作。可能是在线程启动时分配了 ManagedThreadId,因此 #12 是执行时的最后一个是有道理的,因为它实际上是最后一个。

【讨论】:

  • 我故意打乱了我可以随机启动线程的数组。一共10个线程,3个线程一直处于激活状态,其余7个一直在等待,没有机会运行。我就是想知道为什么会出现这种现象。
  • Parallel 将以“随机”方式启动线程,这就是为什么我评论代码以随机播放数组,因为它已经随机播放了 Thread [ ] 你试过了吗我发布的代码?
  • 感谢代码,我试过了,还是一样的现象。我将调试输出的屏幕截图粘贴到线程。 i.stack.imgur.com/QK30N.jpg
  • 有意思,Parallel不应该这样,我自己去查,给我几分钟。
  • 好吧,我认为问题出在Thread.CurrentThread.ManagedThreadId 我会更新我的分析器。
猜你喜欢
  • 2011-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-15
相关资源
最近更新 更多