使用内部同步实现作业列表

时间:2010-01-15 09:13:27

标签: performance multithreading scalability parallel-processing

我正在研究一个简单的作业线程框架,它与id Tech 5 Challenges中描述的框架非常相似。在最基本的层面上,我有一组作业列表,我想在一堆CPU线程中安排这些列表(使用标准线程池进行实际调度。)但是,我想知道这个信号/等待的东西是怎么回事在等待列表中可以有效地实现。据我了解,如果信号令牌尚未执行,则等待令牌会阻止列表执行。这隐含地意味着信号之前的所有内容必须在信号可以被提升之前完成。所以我们假设我们有一个这样的列表:

J1, J2, S, J3, W, J4

然后调度可以这样:

#1: J1, J2, J3
<wait for J1, J2, run other lists if possible>
#2: J4

然而,这并不像看起来那么容易,因为给定一组列表,我将不得不在readywaiting之间移动其中一些,并且还有特殊代码来收集所有在信号之前的作业并在其上标记某些东西,以便当且仅当它们全部完成时它们才能触发信号(意味着例如它不再可能将作业添加到列表,而它是执行,因为以下信号访问以前插入的作业。)

是否有任何“标准”方式有效实施此方法?我也想知道如何最好地安排作业列表执行,现在,每个核心抓取一个作业列表,并安排其中的所有作业,这提供了非常好的扩展(对于32k作业,0.7毫秒,我得到101%,我猜部分原因是单线程版本有时被安排到不同的核心上。)

2 个答案:

答案 0 :(得分:4)

答案 1 :(得分:1)

如果您有权访问您环境中的work stealing framework(例如,如果您使用的是Cilk,或者使用Java中的Doug Lea的fork/join framework),则可以轻松获得一个简单而干净的解决方案(与低级别临时尝试相比,如果您不能使用类似的东西,则可能需要这样做),这可以为您提供自动负载平衡和良好的数据位置。

以下是解决方案的高级描述:每个核心启动一个线程。每个人都被分配一个列表,直到他们筋疲力尽(许多方法都是这样做 - 这是非常好的并发排队机制的任务,这就是你想要尽可能避免自己动手解决的原因)。每个工作人员逐个遍历列表行:   - 维护两个队列,一个用于signal令牌之前的那些作业,一个或之后的作业。   - 遇到作业时,它是分叉,并添加到相应的队列中(取决于我们是否看到了signal令牌)   - 遇到wait令牌时,我们会在信号之前加入所有作业(如果我理解正确,那就是你描述的语义)。请注意,在我使用helpJoin()的代码中, 这意味着该线程实际上会有所帮助(通过弹出分叉任务并执行它们直到连接可以继续)

“Fork”意味着将任务放在线程本地队列中,该队列稍后将由线程本身执行,或者可以被另一个寻找某些工作的线程窃取。

为了便于说明,这里使用前面提到的java框架,对这个场景进行了大约80行的模拟。它创建与可用核心和一些列表一样多的线程,并开始执行它们。注意run()方法有多简单 - 虽然它仍然具有负载平衡的好处,并且线程主要从它们自己的列表中执行任务,除非它们耗尽工作并开始窃取以获取一些。当然,如果你不是Java或C,你必须找到一个类似的框架,但是不管语言如何,同样的核心思想都会同样简化你的代码。

import java.util.*;
import java.util.concurrent.*;
import jsr166y.ForkJoinPool;
import jsr166y.ForkJoinTask;
import jsr166y.RecursiveTask;

public class FJTest {
    public static void main(String[] args) throws Exception {
        Iterable<List<TaskType>> lists = createLists(10);

        ForkJoinPool pool = new ForkJoinPool();

        for (final List<TaskType> list : lists) {
            pool.submit(new Runnable() {
                public void run() {
                    List<ForkJoinTask> beforeSignal = new ArrayList<ForkJoinTask>();
                    List<ForkJoinTask> afterSignal = new ArrayList<ForkJoinTask>();
                    boolean signaled = false;
                    for (TaskType task : list) {
                        switch (task) {
                            case JOB:
                                ForkJoinTask job = new Job();
                                if (signaled == false)
                                    beforeSignal.add(job);
                                else
                                    afterSignal.add(job);
                                job.fork();
                                break;
                            case SIGNAL:
                                signaled = true;
                                break;
                            case WAIT:
                                signaled = false;
                                for (ForkJoinTask t : beforeSignal) {
                                    t.helpJoin();
                                }
                                beforeSignal = afterSignal;
                                afterSignal = new ArrayList<ForkJoinTask>();
                        }
                    }
                }
            });
        }

        pool.shutdown();
        pool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    }

    private static Iterable<List<TaskType>> createLists(int size) {
        List<List<TaskType>> tasks = new ArrayList<List<TaskType>>();
        for (int i = 0; i < size; i++) {
            tasks.add(createSomeList());
        }
        return tasks;
    }

    private static List<TaskType> createSomeList() {
        return Arrays.asList(
                TaskType.JOB,
                TaskType.JOB,
                TaskType.SIGNAL,
                TaskType.JOB,
                TaskType.WAIT,
                TaskType.JOB);
    }

}

enum TaskType {
    JOB, SIGNAL, WAIT;
}
class Job extends RecursiveTask<Void> {
    @Override
    protected Void compute() {
        long x = 1;
        for (long i = 1; i < 200000001; i++) {
            x = i * x;
        }
        System.out.println(x); //just to use x
        return null;
    }
}