Use threads when traversing a tree

孤街醉人 提交于 2019-12-04 19:29:23

Use the Task Parallel Library, rather than rolling your own parallelism code. It is ideally suited to solve this sort of problem.

The way the TPL works is rather than you assigning threads to a problem, you simply break up the problem into "tasks" and let the TPL take care of figuring out how to parallelize the work amongst a pool of available workers. Just make a task for each sub-branch of the tree; those tasks can in turn spawn off tasks of their own for their sub-branches. The TPL will assign threads out of a pool until the processors are saturated.

Because of this, it is important to let the TPL know whether your tasks are going to be gated on the CPU or the I/O:

  • If the tasks are CPU-bound then the TPL will assign one pooled thread per CPU and make the other tasks wait until there is a core available; that maximizes throughput and saturates all the processors. That is exactly what you want: if you bought a machine with four processors and two of them are idle then you paid for two cores that you're not using.

  • If a single task is I/O bound then you can use the LongRunning option when creating the task to indicate to the TPL that this task should not consume an entire core; other tasks should be given a turn at that core.

  • If, as it seems is the case, you have many I/O bound tasks then you should consider using a TaskCompletionSource instead, as that allows for more efficient use of "continuation" callbacks. Consider also using the new async/await feature of C# 5 to schedule continuations; it affords a far more pleasant way of writing the asynchronous code.

And of course, do not forget that if the problem actually is saturating the I/O capability of the machine then no amount of processor parallelism is going to make a dent. If you're filling a swimming pool, adding more hoses to the same faucet doesn't increase the flow through that faucet.

If you want to traverse a tree in parallel, you must:

  • Partition the tree such that separate threads are guaranteed to work on different parts of the tree (e.g. starting at the root, you could assign descendant nodes to new threads until the maximum degree of parallelism has been achieved.
  • Ensure that your tree structure can safely be traversed by multiple threads in general (i.e. traversing does not cause state-altering side effects in the tree implementation).
  • Ensure that no thread is updating the tree during traversal.

If you are getting "strange results", one of the above is probably not true. Keep in mind that the order in which nodes are traversed is non-deterministic in the multi-threaded example. Did you account for that when declaring the results "strange"?

Even so:

  • In the directory example, you may well end up with IO contention limiting effectiveness of your multithreading approach
  • Traversing in-memory nodes will tend to kick things out of the cache, reducing the return on investment of using multiple threads (false sharing).

Keep in mind that multithreading is only useful when your application is taking 100% of CPU time on a single core; if the CPU usage is low (because it's waiting after the hard drive or the network), you won't see any benefit in running your code in parallel.

Recently I had to create an algorithm that is able to discover a huge tree structure (file system actually, but it could be anything) and execute an async operation on every item. I came up with a small library (built using .Net TPL and concurrent queue) that is able to do that:

  • discovers a huge tree in parallel
  • parent items are always processed before children
  • resource usage depends on the given max degree of parallelism, not on tree size
  • works asynchronously

Parallel async TreeWalker

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!