在 parallel.ForEach 循环中获取线程 ID

Posted

技术标签:

【中文标题】在 parallel.ForEach 循环中获取线程 ID【英文标题】:Get a thread id inside parallel.ForEach loop 【发布时间】:2015-11-22 02:29:19 【问题描述】:

有没有办法在 Parallel.FoEach 循环中找到线程 ID。我尝试使用 var threadId = Thread.CurrentThread.ManagedThreadId - 1;,但它没有为我提供我正在寻找的正确索引。

这是一个简单的例子:

private void TestProgram()
    
        int numThreads = 1;

        var values = new List<float>();
        for (int i = 0; i < numThreads; ++i)
        
            values.Add(i);
        

        var data = new List<int>();
        for (int i = 0; i < numThreads; ++i)
        
            data.Add(i);
        


        Parallel.ForEach(data, new ParallelOptionsMaxDegreeOfParallelism = numThreads, i =>
            //foreach (var i in data)
        
            var threadId = Thread.CurrentThread.ManagedThreadId - 1; // make the index to start from 0

            values[threadId] += i;
        );
    

即使设置了MaxDegreeOfParallelism to 1,我仍然得到threadId 大于1。

在上述场景中,有没有办法在 Parallel.ForEach 中找到线程 ID?

注意:我可以在我使用的示例中使用 Parallel.For。但我的问题是在 Parallel.ForEach 中找到它

【问题讨论】:

How to know threadid for each thread spawn by parallel.foreach的可能重复 Parallel.Foreach 是并行任务库的一部分。试试 Task.CurrentId 看看它的行为如何。 【参考方案1】:

由于 Parallel.ForEach 是任务库的一部分,Task.CurrentId 将使您更接近您正在寻找的内容:

   var data = new[]  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;


   Parallel.ForEach(data, new ParallelOptions  MaxDegreeOfParallelism = 4 , i =>
   
            Console.WriteLine(Task.CurrentId);
   );

输出为 1 1 1 1 1 1 1 1 1 1 2 2 1

但是,文档中有免责声明:

任务 ID 是按需分配的,不一定代表 创建任务实例的顺序。请注意,虽然 冲突非常罕见,任务标识符不保证是 独一无二。

【讨论】:

【参考方案2】:

ThreadID 由底层环境分配,不保证从 0 到 [线程数] 甚至每次运行都一致。

只有少数几个关于 threadID 的合同,甚至这些合同也不能保证:

你不会得到 ThreadID 0 ThreadID 1 通常保留给主线程

【讨论】:

【参考方案3】:

您几乎总是会得到一个大于 1 的线程 ID。并行操作将安排在线程池线程上。由于这些线程是在应用程序启动后创建的,因此线程 ID 应该已经启动。

【讨论】:

【参考方案4】:
var data = new[]  0,0,0,0,0,0,0,0,0,0,0,0,0;


    Parallel.ForEach(data, new ParallelOptionsMaxDegreeOfParallelism = 10, i =>
    
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
    );

我得到输出:

180 180 180 180 180 180 180 180 62 62 62 62 180

这是典型的。这是一个环境 ID,与您的 foreach 循环几乎没有关系。您还可以看到,在这种情况下,.NET 不需要达到 MaxDegreeOfParallelism(这是您的另一个假设)。

【讨论】:

【参考方案5】:

由于这在 .NET 中似乎不可用,只需将看似随机的值映射回从零开始的值。

示例如下,您可以根据需要创建资源,每个线程一个。 'locker' 是为了防止线程相互干扰。另请注意,列表应替换为 System.Collections.Concurrent.ConcurrentBag 等线程安全对象

object locker = new object();
List<int> ids = new List<int>();
List<object> resources = new List<object>();

Parallel.For(0, 20000, x =>

    int thread_id = ids.IndexOf(Environment.CurrentManagedThreadId);
    if (thread_id == -1)
                        
        ids.Add(Environment.CurrentManagedThreadId);
        thread_id = ids.IndexOf(Environment.CurrentManagedThreadId);                    
    

    while (resources.Count < thread_id + 1)
    
        lock (locker)
        
            resources.Add(new object());
        
    

    object resource = resources[thread_id];
    // do stuff with the resource
);

【讨论】:

以上是关于在 parallel.ForEach 循环中获取线程 ID的主要内容,如果未能解决你的问题,请参考以下文章

Parallel.ForEach 具有可枚举的 KeyValuePairs?

在 parallel.foreach 循环中捕获的变量

我在 Parallel.ForEach 循环中收到 TaskCanceledException,如何解决?

如何正确调用 Parallel.ForEach 循环中的调用异步方法[重复]

Stringbuilder用于Parallel.Foreach循环

为啥覆盖 Parallel.foreach 循环的 .NET 单元测试依赖于硬件?