【问题标题】:ManualResetEvent and Task IssueManualResetEvent 和任务问题
【发布时间】:2011-02-13 21:04:42
【问题描述】:

我一直在开发使用 HTTP 与客户端通信的自定义多线程服务器。为了创建新线程,我一直在使用 Task.Factory.StartNew() 方法。对于同步,我一直在使用 ManualResetEvent 对象。

下面是代码的精髓:

namespace ThreadTest {
    class Program {        
        private readonly ManualResetEvent _event = new ManualResetEvent(false);

        public void Start() {
            for (int i = 0; i < 100; i++) {
                int num = i;
                Task.Factory.StartNew(() => {
                    Console.WriteLine("Task {0} Started", num);
                    _event.WaitOne();
                });
            }  
        }      

        static void Main(string[] args) {
            var test = new Program();
            test.Start();
            Console.ReadLine();
        }
    }
}

在实际代码中,_event 对象最终会调用 Set 以释放等待的任务。

我遇到的问题是这段代码在我的开发机器上运行良好(它是双核的,每个核心有 2 个线程并使用 MS .NET 4 运行时)但在我的服务器上不起作用(即单核,每核 1 个线程并使用 Mono 2.8 运行时)。我的开发机器上的输出是:

Task 0 Started
Task 1 Started
Task 3 Started
Task 2 Started
Task 4 Started
Task 5 Started
...

虽然服务器输出是

Task 53 Started

我的问题是:我对 ManualResetEvent 或 Tasks 有什么误解?为什么TaskFactory继续在双核上做任务而不是在单核上?

更新:

我刚刚在我的双核(每核 1 个线程)mac 笔记本电脑上试了一下,我得到了输出

Task 97 Started
Task 1 Started

然后什么都没有。我还在我的开发机器(4 个线程)上使用 Mono 进行了尝试,并得到:

Task 99 Started
Task 2 Started
Task 98 Started
Task 0 Started

所以它看起来像 Mono 的一个错误。

【问题讨论】:

  • 一个问题是您对所有任务使用相同的事件。一旦任何单个任务设置了事件,所有其他等待将立即结束。虽然这并不能解释为什么你的服务器只显示一个任务开始。
  • @Jim Mischel:是的,在真正的代码中,我有单独的事件。我只是不明白为什么,在单线程机器上,我一次只能启动 1 个任务。

标签: c# .net multithreading mono


【解决方案1】:

原来这是 Mono 中的一个错误。我filed a report 现在已经修复了。

【讨论】:

  • 值得注意的是,TaskScheduler 实现不需要启动线程或提供有关任务执行顺序的任何保证。排序的唯一保证是使用任务延续定义的,TaskScheduler 必须遵守。因此,这不是 Mono 中的错误,因为它与 MS 默认实现不一致。你的代码不应该依赖于实现的细节,只要它们满足相对宽松的 TaskScheduler 契约。
【解决方案2】:

您的代码永远不会完成任何任务,因为事件永远不会发出信号。最终,它用完了要分配的线程并锁定。问题实际上是“当没有线程可以继续时,任务并行库在不同的 CPU 配置下启动了多少线程”,因为这是您的代码有效测量的内容。

任务并行库的默认调度程序基于ThreadPool,它使用爬山算法来调整线程数,以最大限度地提高工作项完成率。该算法会考虑您拥有的核心数。

有更好的方法可以使用任务并行库来安排在任务完成后发生的工作,这些方法不会占用线程,例如 ContinueWith。您想通过事件构造实现什么目标?

【讨论】:

  • 代码只是行为的本质。正如问题中所说,在实际代码中,我实际上在某个时候调用Set 并让这些任务完成。
  • 但是你是否称它为 before 线程饥饿已经出现? TPL 有时会在调用线程上运行任务,如果这是唯一可以设置它的线程,也许这就是问题所在。单核机器似乎已经启动了 53 个任务,然后才停止并可能在调用线程上运行任务 53。
  • 我怀疑 Mono 实现实际上是将任务放入堆栈或无序集合中,然后在多个任务已经“排队”后抓取一个开始。这可能是试图提供“公平”调度,而不是 FIFO 调度到可用线程。
猜你喜欢
  • 2013-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多