我一直在修补BSP树一段时间,我也在玩线程。向BSP树添加三角形时,会出现创建新线程的机会,以便并行处理数据。
insert(triangle, bspnode) { .... else if(triangle spans bspnode) { (frontpiece, backpiece) = plane_split(triangle, bspnode) insert(frontpiece, bspnode.front) insert(backpiece, bspnode.back) } .... }
上面的两个插入操作可以由两个线程执行,由于它们不会修改相同的数据,因此可以使用廉价的同步。
insert(triangle, bspnode) { .... else if(triangle spans bspnode) { (frontpiece, backpiece) = split(triangle, bspnode) handle = beginthread(insert(backpiece, bspnode.front)) insert(frontpiece, bspnode.back) if(handle) { waitforthread(handle) } else { insert(backpiece, bspnode.front) } } .... }
这个新方法尝试创建一个并行完成操作的线程,但是如果无法创建线程则不应该失败(它只会恢复到原始算法)。
这是一种合理的编程习惯,还是我使用的线程不正确?我还没有找到关于这种技术的任何文献。我喜欢它倾向于使用我的CPU(2核),理论上可以扩展到任何数量的可用处理器。我不喜欢它在CPU和内存上可能非常浪费。
答案 0 :(得分:12)
如果处理的某些部分正在等待外部事务(用户输入,I / O,其他一些处理),则线程很好 - 正在等待的线程可以继续等待,而一个没有等待前进的线程
但是,对于处理密集型任务,比处理器更多的线程实际上会产生开销。看起来你的线程正在做所有“CPU工作”,所以我坚持每个核心一个线程 - 测试以找到最佳数量。
创建的最大开销来自上下文切换(冻结一个线程并加载下一个线程的执行上下文),以及线程执行具有不同内存的任务时的缓存未命中(如果您的线程可以有效地使用CPU缓存)
答案 1 :(得分:2)
最好的办法是创建一个线程池,然后“透明地”使用它来添加节点。
例如,在程序启动时创建2个线程,让它们等待信号量或事件。当您要添加节点时,将数据弹出到队列中然后触发信号量。这会唤醒其中一个线程,它将数据从队列中弹出并执行处理。 (确保对队列的访问是线程安全的 - 最好与关键部分完全同步)。
你的应用程序的整体性能较慢,因为你有更多的开销,将数据复制到队列和运行额外的线程,但如果你曾经在单核上运行,你现在将运行2.它最好的如果线程处理很昂贵。
答案 2 :(得分:0)
当然,例如,Quicksort可以很容易地编程多线程,并在多核系统上获得一些大的性能提升,在非多线程系统上有一些小的性能损失。请记住,你现在要增加两次开销 - 一次是为了保存递归,一次是在线程上,所以如果你进行了大量的递归,那么它可能比非多线程方法更快地压倒系统。