【发布时间】:2019-04-27 13:07:20
【问题描述】:
我正在扫描一些目录中的项目。我刚刚阅读了Multithreaded Directory Looping in C# 的问题,但我仍然想让它成为多威胁的。尽管每个人都说驱动器将成为瓶颈,但我有一些观点:
- 驱动器可能大多是“单线程”的,但您怎么知道它们将来会带来什么?
- 您如何知道您正在扫描的不同子路径是同一个物理驱动器?
- 我在
System.IO上使用了一个抽象层(甚至两个),以便以后可以在不同的场景中重用代码。
所以,我的第一个想法是使用 Task,第一个虚拟实现是这样的:
public async Task Scan(bool recursive = false) {
var t = new Task(() => {
foreach (var p in path.scan) Add(p);
if (!recursive) return;
var tks = new Task[subs.Count]; var i = 0;
foreach (var s in subs) tks[i++] = s.Scan(true);
Task.WaitAll(tks);
}); t.Start();
await t;
}
我不喜欢为每个项目创建一个Task 的想法,通常这似乎并不理想,但这只是为了测试,因为任务被宣传为自动管理线程...
此方法有效,但速度很慢。它需要 5s 才能完成,而下面的单个受威胁版本大约需要 0.5s 才能在同一数据集上完成整个程序:
public void Scan2(bool recursive = false) {
foreach (var p in path.scan) Add(p);
if (!recursive) return;
foreach (var s in subs) s.Scan2(true);
}
我徘徊在拳头方法真正出了什么问题。机器未加载,CUP 使用量微不足道,驱动器很好...我尝试使用 NProfiler 对其进行分析,除了程序一直位于 Task.WaitAll(tks) 之外,它并没有告诉我太多。
我还编写了一个线程锁定计数机制,在添加每个项目期间调用该机制。也许是它的问题?
#region SubCouting
public Dictionary<Type, int> counters = new Dictionary<Type, int>();
private object cLock = new object();
private int _sc = 0;
public int subCount => _sc;
private void inCounter(Type t) {
lock (cLock) {
if (!counters.ContainsKey(t)) counters.Add(t, 1);
counters[t]++;
_sc++;
}
if (parent) parent.inCounter(t);
}
#endregion
但是即使线程在这里等待,执行时间会不会类似于单线程版本而不是慢 10 倍?
我不确定如何处理这个问题。如果我不想使用任务,是否需要手动管理线程,或者是否已经有一些库非常适合这项工作?
【问题讨论】:
-
我怀疑启动这么多任务(you should be using
Task.Run,不创建任务实例,.Starting 他们)正在创建线程池作业的积压,导致线程池饥饿(线程池有有限数量的线程和一个队列...只有当很明显队列没有缩小时,它才会启动额外的线程...这种测量需要时间,导致您的应用程序出现延迟)。确实没有理由为 IO 手动生成额外的线程......您的应用程序将能够跟上。考虑专用的async... -
... 您正在使用的文件 IO 命令的版本。并停止滥用线程。
-
顺便说一句,我投票结束,因为虽然这个问题是关于文件 IO,但在你的问题中没有任何与文件 IO 相关的代码。
-
滥用线程非常切中要害。线程用于分散工作负载,这样做有其成本。这里没有你可以分散的工作量,所以你得到它的成本。
-
这个问题不是关于文件
IO,而是关于使用多线程的递归算法。我以为我说得很清楚。现在我在文件系统上使用算法,但这是一个独立于IO的抽象层,我可以将底层系统切换到其他系统。
标签: c# multithreading recursion tree