什么决定了 TaskFactory 产生的作业的线程数?
What determines the number of threads for a TaskFactory spawned jobs?
我有以下代码:
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 个线程(基于观察)。我的问题:
- 什么决定了一次使用的线程数?
- 如何找回这个号码?
- 如何更改此号码?
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}");
}
当您在 .NET 中使用 Task
时,您是在告诉 TPL 安排一项工作(通过 TaskScheduler
)在 ThreadPool
上执行。请注意,工作将尽早安排,但安排程序认为合适。这意味着 TaskScheduler
将决定使用多少个线程 运行 n
个任务以及哪个任务在哪个线程上执行。
TPL 调整得非常好,并会在执行您的任务时继续调整其算法。因此,在大多数情况下,它会尽量减少争用。这意味着如果你正在 运行 执行 100 个任务并且只有 4 个核心(你可以使用 Environment.ProcessorCount
),那么在任何给定时间执行超过 4 个线程是没有意义的,因为否则它需要做更多的上下文切换。现在有时您想要显式覆盖此行为。假设您需要等待 某种 IO 完成,这是一个完全不同的故事。
总之,信任TPL。但是如果你坚持为每个任务生成一个线程(并不总是一个好主意!),你可以使用:
Task.Factory.StartNew(
() => /* your piece of work */,
TaskCreationOptions.LongRunning);
这告诉 DefaultTaskscheduler
显式为该工作生成一个新线程。
您也可以使用自己的 Scheduler
并将其传递给 TaskFactory
。你可以找到一大堆 Schedulers
HERE
.
请注意,另一种替代方法是使用 PLINQ
,它会再次默认分析您的查询并决定并行化它是否会产生任何好处,同样在这种情况下阻塞 IO 的其中你确定启动多个线程会导致更好的执行你可以通过使用 WithExecutionMode(ParallelExecutionMode.ForceParallelism)
强制并行性然后你可以使用 WithDegreeOfParallelism,提示要使用多少线程但是请记住不能保证你会得到那么多线程,因为MSDN 说:
Sets the degree of parallelism to use in a query. Degree of
parallelism is the maximum number of concurrently executing tasks that
will be used to process the query.
最后,我高度推荐阅读THIS
关于Threading
和TPL
的精彩系列文章。
如果您将任务数量增加到例如 1000000,您将看到随着时间的推移产生更多的线程。 TPL 倾向于每 500 毫秒注入一个。
TPL 线程池不理解 IO-bound 工作负载(睡眠是 IO)。在这些情况下,依靠 TPL 来选择正确的并行度并不是一个好主意。 TPL 完全没有头绪,并根据对吞吐量的模糊猜测注入更多线程。也是为了避免死锁。
在这里,TPL 策略显然没有用,因为您添加的线程越多,您获得的吞吐量就越大。在这种人为设计的情况下,每个线程每秒可以处理一个项目。 TPL 对此一无所知。将线程数限制为核心数是没有意义的。
What determines the number of threads used at a time?
几乎没有记录 TPL 启发式。他们经常出错。特别是在这种情况下,随着时间的推移,它们会产生 无限数量的线程 。使用任务管理器自己看看。让这个 运行 一个小时,您将拥有 1000 个线程。
How can I retrieve this number? How can I change this number?
您可以检索这些数字中的 一些 ,但这不是正确的方法。如果您需要有保证的 DOP,您可以使用 AsParallel().WithDegreeOfParallelism(...)
或自定义任务计划程序。您也可以手动启动 LongRunning
任务。不要弄乱进程全局设置。
我有以下代码:
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 个线程(基于观察)。我的问题:
- 什么决定了一次使用的线程数?
- 如何找回这个号码?
- 如何更改此号码?
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}");
}
当您在 .NET 中使用 Task
时,您是在告诉 TPL 安排一项工作(通过 TaskScheduler
)在 ThreadPool
上执行。请注意,工作将尽早安排,但安排程序认为合适。这意味着 TaskScheduler
将决定使用多少个线程 运行 n
个任务以及哪个任务在哪个线程上执行。
TPL 调整得非常好,并会在执行您的任务时继续调整其算法。因此,在大多数情况下,它会尽量减少争用。这意味着如果你正在 运行 执行 100 个任务并且只有 4 个核心(你可以使用 Environment.ProcessorCount
),那么在任何给定时间执行超过 4 个线程是没有意义的,因为否则它需要做更多的上下文切换。现在有时您想要显式覆盖此行为。假设您需要等待 某种 IO 完成,这是一个完全不同的故事。
总之,信任TPL。但是如果你坚持为每个任务生成一个线程(并不总是一个好主意!),你可以使用:
Task.Factory.StartNew(
() => /* your piece of work */,
TaskCreationOptions.LongRunning);
这告诉 DefaultTaskscheduler
显式为该工作生成一个新线程。
您也可以使用自己的 Scheduler
并将其传递给 TaskFactory
。你可以找到一大堆 Schedulers
HERE
.
请注意,另一种替代方法是使用 PLINQ
,它会再次默认分析您的查询并决定并行化它是否会产生任何好处,同样在这种情况下阻塞 IO 的其中你确定启动多个线程会导致更好的执行你可以通过使用 WithExecutionMode(ParallelExecutionMode.ForceParallelism)
强制并行性然后你可以使用 WithDegreeOfParallelism,提示要使用多少线程但是请记住不能保证你会得到那么多线程,因为MSDN 说:
Sets the degree of parallelism to use in a query. Degree of parallelism is the maximum number of concurrently executing tasks that will be used to process the query.
最后,我高度推荐阅读THIS
关于Threading
和TPL
的精彩系列文章。
如果您将任务数量增加到例如 1000000,您将看到随着时间的推移产生更多的线程。 TPL 倾向于每 500 毫秒注入一个。
TPL 线程池不理解 IO-bound 工作负载(睡眠是 IO)。在这些情况下,依靠 TPL 来选择正确的并行度并不是一个好主意。 TPL 完全没有头绪,并根据对吞吐量的模糊猜测注入更多线程。也是为了避免死锁。
在这里,TPL 策略显然没有用,因为您添加的线程越多,您获得的吞吐量就越大。在这种人为设计的情况下,每个线程每秒可以处理一个项目。 TPL 对此一无所知。将线程数限制为核心数是没有意义的。
What determines the number of threads used at a time?
几乎没有记录 TPL 启发式。他们经常出错。特别是在这种情况下,随着时间的推移,它们会产生 无限数量的线程 。使用任务管理器自己看看。让这个 运行 一个小时,您将拥有 1000 个线程。
How can I retrieve this number? How can I change this number?
您可以检索这些数字中的 一些 ,但这不是正确的方法。如果您需要有保证的 DOP,您可以使用 AsParallel().WithDegreeOfParallelism(...)
或自定义任务计划程序。您也可以手动启动 LongRunning
任务。不要弄乱进程全局设置。