什么决定了TaskFactory衍生作业的线程数?

时间:2016-01-20 23:32:22

标签: c# .net multithreading task-parallel-library

我有以下代码:

var factory = new TaskFactory();
for (int i = 0; i < 100; i++)
{
    var i1 = i;
    factory.StartNew(() => foo(i1));
}

static void foo(int i)
{
    Thread.Sleep(1000);
    Console.WriteLine($"foo{i} - on thread {Thread.CurrentThread.ManagedThreadId}");
}            

我可以看到它一次只做4个线程(基于观察)。我的问题:

  1. 什么决定了一次使用的线程数?
  2. 如何找回此号码?
  3. 如何更改此号码?
  4. P.S。我的盒子有4个核心。

    P.P.S。我需要有一些特定数量的任务(而不是更多)由TPL同时处理,最后得到以下代码:

    private static int count = 0;   // keep track of how many concurrent tasks are running
    
    private static void SemaphoreImplementation()
    {
        var s = new Semaphore(20, 20);  // allow 20 tasks at a time
    
        for (int i = 0; i < 1000; i++)
        {
            var i1 = i;
    
            Task.Factory.StartNew(() =>
            {
                try
                {                        
                    s.WaitOne();
                    Interlocked.Increment(ref count);
    
                    foo(i1);
                }
                finally
                {
                    s.Release();
                    Interlocked.Decrement(ref count);
                }
            }, TaskCreationOptions.LongRunning);
        }
    }
    
    static void foo(int i)
    {
        Thread.Sleep(100);
        Console.WriteLine($"foo{i:00} - on thread " + 
                $"{Thread.CurrentThread.ManagedThreadId:00}. Executing concurently: {count}");
    }
    

2 个答案:

答案 0 :(得分:15)

当您在.NET中使用Task时,您告诉TPL安排一项工作(通过TaskScheduler)在ThreadPool上执行。请注意,工作将尽早安排,但调度程序认为合适。这意味着TaskScheduler将决定将使用多少个线程来运行n个任务,以及在哪个线程上执行哪个任务。

TPL经过精心调整,并在执行任务时继续调整算法。因此,在大多数情况下,它会尽量减少争用。这意味着如果您运行100个任务并且只有4个核心(可以使用Environment.ProcessorCount获得),那么在任何给定时间执行4个以上的线程都没有意义,否则它将需要做更多的上下文切换。现在有时您希望显式覆盖此行为。让我们假设您需要等待某种IO完成,这是一个完全不同的故事

总之,请相信TPL。但如果你坚持每个任务产生一个线程(并不总是一个好主意!),你可以使用:

Task.Factory.StartNew(
    () => /* your piece of work */, 
    TaskCreationOptions.LongRunning);

这告诉默认 Taskscheduler为该工作明确生成一个新线程。

您也可以使用自己的Scheduler并将其传递给TaskFactory。你可以找到一大堆Schedulers HERE

注意另一个替代方法是使用 PLINQ ,默认情况下再次分析您的查询并决定是否并行化会产生任何好处,再次在阻塞IO的情况下您确定启动多个线程将导致更好的执行,您可以使用 WithExecutionMode(ParallelExecutionMode.ForceParallelism) 强制并行,然后您可以使用 WithDegreeOfParallelism 来提供有关多少线程的提示线程使用但是记住无保证你会得到那么多线程,正如MSDN所说:

  

设置要在查询中使用的并行度。程度   parallelism是同时执行任务的最大数量   将用于处理查询。

最后,高度建议在ThreadingTPL上阅读THIS一系列精彩文章。

答案 1 :(得分:4)

如果您将任务数量增加到例如1000000,您将看到随着时间推移产生更多线程。 TPL倾向于每500毫秒注入一个。

TPL线程池不了解IO绑定工作负载(睡眠是IO)。在这些情况下依靠TPL选择正确的并行度并不是一个好主意。 TPL是完全无能为力的,并且基于对吞吐量的模糊猜测注入更多线程。还要避免死锁。

这里,TPL策略显然没有用,因为您添加的线程越多,您获得的吞吐量就越多。在这个设计的案例中,每个线程可以每秒处理一个项目。 TPL对此并不了解。将线程数限制为核心数是没有意义的。

  

什么决定了一次使用的线程数?

几乎没有记录TPL启发式。他们经常出错。特别是在这种情况下,它们会随着时间的推移产生无限数量的线程。使用任务管理器自己查看。让它运行一个小时,你将拥有1000个线程。

  

如何检索此号码?我该如何更改此号码?

您可以检索这些数字,但这不是正确的方法。如果您需要有保证的DOP,可以使用AsParallel().WithDegreeOfParallelism(...)或自定义任务调度程序。您还可以手动启动LongRunning个任务。不要搞乱流程全局设置。