线程休眠会影响其他线程吗?

10

这里有一个控制台程序,想要启动10个线程并批量运行,在等待5秒后再次批量停止。

static void Main(string[] args)
{
    System.Threading.Tasks.Parallel.For(0, 10, (index) =>
    {
        Action<int> act = (i) =>
        {
            Console.Write("start {0} ", i);
            Thread.Sleep(5000);
        };

        act.BeginInvoke(index, OnTaskComplete, index);
    });

    Console.ReadKey();
}

static void OnTaskComplete(IAsyncResult res)
{
    Console.Write("finish {0} ", res.AsyncState);
}

但是结果并不如我所期望的,10个线程逐一缓慢启动(大约1秒的间隔),甚至有些"完成"出现在一些"开始"之前。

当注释掉 Thread.Sleep 后,所有线程都在瞬间开始和结束。

Thread.Sleep 是否会影响其他线程?有没有办法让它纯粹地空闲等待?

/-----------------------------编辑-----------------------------

同样的问题也出现在:

static void Main(string[] args)
{
    System.Threading.Tasks.Parallel.For(0, 10, (index) =>
    {
        Console.Write("start {0} ", index);
        Thread.Sleep(5000);
        Console.Write("fnish {0} ", index);
    });

    Console.ReadKey();
}

----------------------编辑------------------------

最终我找到了一种可爱的方法来替代 thread.sleep。

static void Main(string[] args)
{
    System.Threading.Tasks.Parallel.For(0, 10, (index) =>
    {
        Console.Write("start {0} ", index);

        var t1 = new System.Threading.Timer(new TimerCallback(MyTimerCallback), index, 5000, 0); 
    });

    Console.ReadKey();
}

static void MyTimerCallback(object o)
{
    Console.Write("Timer callbacked ");
}

你能定义纯空闲时间吗?Thread.Sleep 只保证线程至少休眠指定的时间。如果 ThreadA 和 ThreadB 同时休眠相同的时间,不能保证它们会以相反的顺序唤醒。 - parapura rajkumar
1
只是好奇 -- 为什么要使用 BeginInvoke 执行 act?为什么不直接将动作中的代码放在第一个回调中,并在结尾处调用 OnTaskComplete(因为如果我理解正确,回调已经在另一个线程上异步执行了)? - Cameron
1
@raj。我想知道的是为什么这些线程一个接一个地慢慢启动(启动这些线程大约需要10秒钟)。我认为它们应该批量开始。 - demaxSH
3个回答

13

这是有意为之的。您看到的是线程池管理器试图保持有限数量的线程处于执行状态。确保您的程序不要运行超过计算机cpu核心数量的线程非常重要。当Windows被迫开始在活动线程之间交换核心时,效率低下,工作量变少。线程池管理器并不聪明到知道该线程正在休眠而不是实际执行任何工作。

在双核机器上,您会看到前两个线程立即启动。然后当线程管理器注意到活动线程没有取得任何进展且可能被阻塞时,允许单个线程以一秒的时间间隔运行。释放线程并执行Console.Write()调用的顺序是不确定的。

当然,这是人为测试,真正的线程不会休眠。如果您有需要长时间阻塞的线程,例如等待完成I/O请求,那么使用线程池线程(任务)不是最佳解决方案。


我猜如果 CPU 核心数量少于线程数,线程将按时间片进行处理。我发现前两个线程同时开始,但第三个和第四个线程在一两秒后才流动,这似乎太长了。可能是因为线程休眠无法很好地进行时间切片。 - demaxSH
不,你错过了答案的关键部分。它是线程池管理器防止线程在核心繁忙时启动的。 - Hans Passant
1
实际上,我正在尝试模拟一个I/O请求等待的场景,以进行单元测试。而我找不到比使用thread.sleep更好的方法。 - demaxSH
这是一个可以接受的线程池调度队列模拟,支持被阻塞在某些操作上的线程。线程池管理器不会启动超过最小配置线程数的线程,直到清楚排队的委托无法快速得到服务。 - Jake T.

6

TaskCreationOptions.LongRunning将“移除”线程池限制。
我不知道如何轻松地为Parallel.For指定TaskCreationOptions.LongRunning
但是,您可以使用Task类实现相同的效果:

Action<int> action = i =>
    {
        Console.Write("start {0} ", i);
        Thread.Sleep(5000);
        Console.Write("finish {0} ", i);
    };

var tasks = Enumerable.Range(0, 100)
    .Select(arg => Task.Factory.StartNew(() => action(arg), TaskCreationOptions.LongRunning))
    .ToArray();

Task.WaitAll(tasks);

没有使用TaskCreationOptions.LongRunning,它的运行方式与您的Parallel.For完全相同。

3

我稍微更新了代码,以在写入控制台时显示线程ID:

Console.WriteLine("start index:{0} thread id:{1} Time:{2} ", index, Thread.CurrentThread.ManagedThreadId.ToString(), DateTime.Now.ToLongTimeString());
Thread.Sleep(5000);
ConsoleWriteLine("finish index:{0} thread id:{1} Time:{2} ", index, Thread.CurrentThread.ManagedThreadId.ToString(), DateTime.Now.ToLongTimeString());

我的机器是双核的,这是我得到的输出。这应该让你了解正在发生的事情。请记住,循环可能不总是按顺序运行,即并行运行0到9,它会获取数组的一块并通过lambda运行每个项目。

输出:

start  index:1 thread id:11 Time:11:07:17 PM
start  index:0 thread id:9  Time:11:07:17 PM
start  index:5 thread id:10 Time:11:07:17 PM
start  index:6 thread id:12 Time:11:07:18 PM
start  index:2 thread id:13 Time:11:07:19 PM
start  index:7 thread id:14 Time:11:07:20 PM
start  index:3 thread id:15 Time:11:07:21 PM
start  index:8 thread id:16 Time:11:07:22 PM
finish index:0 thread id:9  Time:11:07:22 PM
start  index:4 thread id:9  Time:11:07:22 PM
finish index:1 thread id:11 Time:11:07:22 PM
start  index:9 thread id:11 Time:11:07:22 PM
finish index:5 thread id:10 Time:11:07:22 PM
finish index:6 thread id:12 Time:11:07:23 PM
finish index:2 thread id:13 Time:11:07:24 PM
finish index:7 thread id:14 Time:11:07:25 PM
finish index:3 thread id:15 Time:11:07:26 PM
finish index:8 thread id:16 Time:11:07:27 PM
finish index:4 thread id:9  Time:11:07:27 PM
finish index:9 thread id:11 Time:11:07:27 PM

好的,顺序并不重要。但是我们可以看到一些线程在5秒后开始!这不是并行启动。我认为最新的一个在100毫秒后启动是可以接受的。 - demaxSH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接