多处理器编程的艺术(二)-并行程序设计

当处理器的性能的发展受到各方面因素的限制的时候,计算机产业开始用多处理器结构实现并行计算来提高计算的效率。我们使用多处理器共享存储器的方式实现了多处理器编程,也就是多核编程。当然在这样的系统结构下我们面临着各种各样的挑战,例如如何协调各个处理器之间的数据调度以及现代计算机系统固有的异步特征等等。

在接下来的一系列文章中,我将会介绍一些基础的原理以及并行程序的设计和并发程序的设计及实现,写这篇文章是对近期学习课程的总结,方便自己温故时习,感谢USTC付明老师的《多核并行计算》课程,了解更多推荐《The Art of Multiprocessor Programming, Revised Reprint》。

实例:大数组元素的求和

思想:给出4个线程同时对数组的1/4求和。

  • 注意:这是一个低级的算法
  • 创建4个线程,每个线程负责部分的工作
  • 调用start(),启动每个线程并行的运行
  • 使用join()方法等待每个线程运行结束
  • 将4个结果相加在一起
class SumThread extends java.lang.Thread {
  int lo, int hi, int[] arr; // arguments
  int ans = 0; // result
  SumThread(int[] a, int l, int h) { … }
  public void run(){ … } // override
}
int sum(int[] arr){// can be a static method
  int len = arr.length;
  int ans = 0;
  SumThread[] ts = new SumThread[4];
  for(int i=0; i < 4; i++){// do parallel computations
    ts[i] = new SumThread(arr,i*len/4,(i+1)*len/4);
    ts[i].start(); //必须使用start(),而不是run()方法
  }
  for(int i=0; i < 4; i++) { // combine results
    ts[i].join(); // wait for helper to finish!
    ans += ts[i].ans;
  }
  return ans;
}

join()方法使得调用者阻塞,直到receiver 完成了执行;

线程的启动需要用start(),而不是run()

Fork-join framework

Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。比如计算1+2+…+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。

  • Fork-join 程序不需要关注线程间的共享内存

EX:求一个数组的最大值。

多线程方法:

public class MyThread implements Runnable {

    //下面两个静态成员变量需要通过用关键字synchronized修饰 的方法来访问
    private volatile static int max = Integer.MIN_VALUE;            //初始最大值

    //下面为成员变量,每个线程对象私有
    private int[] nums;            //待查找数组
    private int low;                  //当前线程对象需要处理的数组开始下标
    private int high;                 //当前线程对象需要处理的数组结束下标

    //构造方法,传入待查找数组、开始下标和结束下标
    public MyThread(int[] nums, int low, int high){
        this.nums = nums;
        this.low = low;
        this.high = high;
    }

    @Override
    public synchronized void run() {

        for(int i=low;i<=high;i++)
        {
            if(nums[i] > max)
            {
                MyThread.setMax(nums[i]);
            }
        }

    }

    public static synchronized int getMax() {
        return max;
    }

    public static synchronized void setMax(int max) {
        MyThread.max = max;
    }

}

Fork-join框架:

public class ForkThread extends RecursiveTask<Integer> {

    private static final int SEQUENTIAL_THRESHOLD = 5;//子数组到分割最终大小

    private final int[] data;
    private final int start;
    private final int end;

public ForkThread(int[] data, int start, int end) {
  this.data = data;
  this.start = start;
  this.end = end;
}

public ForkThread(int[] data) {
  this(data, 0, data.length);
}

    @Override
    protected Integer compute() {
        final int length = end - start;
        if (length < SEQUENTIAL_THRESHOLD) {
            return computeDirectly();
        }
        final int split = length / 2;
        final ForkThread left = new ForkThread(data, start, start + split);
        left.fork();
        final ForkThread right = new ForkThread(data, start + split, end);
        return Math.max(right.compute(), left.join());
    }

private Integer computeDirectly() {
  /*System.out.println(Thread.currentThread() + "computing: " + start
                     + " to " + end);*/
  int max = Integer.MIN_VALUE;
  for (int i = start; i < end; i++) {
    if (data[i] > max) {
      max = data[i];
    }
  }
  return max;
}

    public static void main(String[] args) {
        // create a random data set
        final int[] data = new int[10000];
        final Random random = new Random();
        for (int i = 0; i < data.length; i++) {
            data[i] = (int)Math.floor((random.nextDouble()*100000.0));
            System.out.println(data[i]);
        }
        /*int data[] = new int[10000];
        for(int i=0;i<10000;i++)
            data[i] =(int) Math.random()*10000;*/

        // submit the task to the pool
        final ForkJoinPool pool = new ForkJoinPool(4);
        final ForkThread finder = new ForkThread(data);
//      System.out.println(pool.invoke(finder));
    }
}

Main方法线程:

public class SearchMax {

    /**
     * @param args
     */
    //初始化数组
    public static int[] InitialArr(){
        final int[] data = new int[10000];
        final Random random = new Random();
        for (int i = 0; i < data.length; i++) {
            data[i] = (int)Math.floor((random.nextDouble()*100000.0));
        }
        return data;
    }
    //分治法
    public static long DivideMax(int arr[]){

        int size = arr.length;
        long startTime=System.nanoTime();
        Thread t1 = new Thread(new MyThread(arr,0,size/2));
        Thread t2 = new Thread(new MyThread(arr,size/2+1,size-1));

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime=System.nanoTime();
        System.out.println( "分治法: "+MyThread.getMax());

        return (endTime-startTime);
    }

    //顺序查找
    public static long OrderMax(int arr[]){

        int size = arr.length;
        int ret = 0;
        long startTime=System.nanoTime();
        for(int i=0;i<size;i++){
            if(arr[i]>ret)
                ret = arr[i];
        }
        long endTime=System.nanoTime();
        System.out.println( "顺序查找: "+ret);
        return (endTime-startTime);
    }

    //Fork-Join框架
    public static long ForkMax(int arr[]){

        int ret = 0;
        long startTime=System.nanoTime();
        // submit the task to the pool
        final ForkJoinPool pool = new ForkJoinPool(4);
        final ForkThread finder = new ForkThread(arr);

        ret = pool.invoke(finder);
        long endTime=System.nanoTime();
        System.out.println( "Fork_join: "+ret);
        return (endTime-startTime);
    }

    public static void main(String[] args) {

        int data[] = InitialArr();
        System.out.println("分治法花费时间 "+DivideMax(data)+" ns");
        System.out.println("顺序查找花费时间 "+OrderMax(data)+" ns");
        System.out.println("Fork_Join花费时间 "+ForkMax(data)+" ns");

    }

}

运行结果:

设计一个Fork/Join框架的步骤:

第一步:分割任务。首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割的子任务足够小;

第二步:执行任务并合并结果,分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里分别获取任务执行,子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。

Fork/Join框架使用两个类来完成以上两件事:

(1)ForkJoinTask:我们要使用Fork Join框架,必须首先创建一个Fork Join任务,它提供在任务中执行fork() 和join()操作的机制,通常情况下我们不需要继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供以下两个子类:

  • RecursiveAction:用于没有返回结果的任务。
  • RecursiveTask:用于有返回结果的任务。

(2)ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。

Fork/Join框架的异常处理:

Fork JoinTask在执行的时候可能会抛出异常,但是我们没办法在主线程里直接捕获异常,所以Fork JoinTask提供了isCompletedAbnormally()方法来检查任务是否已经抛出异常或已经被取消了,并且可以通过Fork JoinTask的getException方法获取异常,使用如下代码:

if(task.isCompletedAbnormally())

{

System.out.println(task.getException());

}

getException方法返回Throwable对象,如果任务被取消了则返回CancellationException,如果任务没有完成或者没有抛出异常则返回null。

EX:大数组求和

class SumArray extends RecursiveTask<Integer> {
  int lo; int hi; int[] arr; // arguments
  SumArray(int[] a, int l, int h) { … }
  protected Integer compute(){// return answer
    if(hi – lo < SEQUENTIAL_CUTOFF) {
      int ans = 0;
      for(int i=lo; i < hi; i++)
        ans += arr[i];
      return ans;
    } else {
      SumArray left = new SumArray(arr,lo,(hi+lo)/2);
      SumArray right= new SumArray(arr,(hi+lo)/2,hi);
      left.fork();
      int rightAns = right.compute();
      int leftAns  = left.join();
      return leftAns + rightAns;
    }
  }
}
static final ForkJoinPool fjPool = new ForkJoinPool();
int sum(int[] arr){
  return fjPool.invoke(new SumArray(arr,0,arr.length));
}

算法设计

时间: 2024-10-13 15:03:17

多处理器编程的艺术(二)-并行程序设计的相关文章

《java并发编程的艺术》读书笔记-第三章Java内存模型(二)

一概述 本文属于<java并发编程的艺术>读书笔记系列,第三章java内存模型第二部分. 二final的内存语义 final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.可以参照之前整理的关键字final.这里作者主要介绍final域的内存语义. 对于final域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这

OpenMP并行程序设计——for循环并行化详解

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/40018735 在C/C++中使用OpenMP优化代码方便又简单,代码中需要并行处理的往往是一些比较耗时的for循环,所以重点介绍一下OpenMP中for循环的应用.个人感觉只要掌握了文中讲的这些就足够了,如果想要学习OpenMP可以到网上查查资料. 工欲善其事,必先利其器.如果还没有搭建好omp开发环境的可以看一下OpenMP并行程序设计--Eclipse开发环境的搭建 首

读《Java并发编程的艺术》(一)

离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督促自己要不断的学习,不断的进步. 最近在进一步学习Java并发编程,不言而喻,这部分内容是很重要的.现在就以<并发编程的艺术>一书为主导线,开始新一轮的学习. 进程和线程 进程是一个应用程序在处理机上的一次执行过程,线程是进程的最小基本单位(个人理解).一个进程可以包含多个线程. 上下文切换 我们

C#编程总结(二)多线程基础

C#编程总结(二)多线程基础 无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应用户操作,同时在用户事件之间或者甚至在用户事件期间利用处理器,最强大的方式之一是使用多线程技术. 多线程:线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.如果某个线程进行一次长延迟操作, 处理器就切换到另一个线程执行.这样,多个线程的并行(并发)执行隐藏了

ActionScript3游戏中的图像编程(连载二十)

1.4.2 灰度的计算方法 回过头来看RGB,站在科学的角度来解释,它们确实也有更明亮的理由,因为下面一排色彩反射出来的色光总量是上一排色的两倍.      为此,作者曾自作聪明地发明了一条“原创”的灰度公式: Gray=(r+g+b)/3 哈哈,用色光总量来表达颜色的灰度想必就比较准确了吧!沾沾自喜一番以后,我还试着用这条自创的定律来转换这张测试图片,上下色块的灰度果然拉开了,可是很不幸地,左右相邻,边界分明的色块依然粘连在一块(图 1.30). 图 1.30 笔者“自创”的灰度转换 显然此法

ActionScript3游戏中的图像编程(连载二十一,第1章完)

1.4.3 用灰度/明度指导色彩搭配 试着只通过调整不等于0的通道,让上一排的色彩在灰度上保持一致,把三个颜色分别代入到心理学公式.得 Gray(red) = r * 0.299      Gray(green) = g * 0.587      Gray(blue) = b * 0.114 让它们灰度相等,则 r * 0.299 = g * 0.587 = b * 0.114 可见,b值一定最大,不妨让b取最大值255,求得r=97, g=50.把这两个值分别应用到红和绿两个色块上. 绿色不再

翻新并行程序设计的认知整理版(state of the art parallel)

近几年,业内对并行和并发积累了丰富的经验,有了较深刻的理解.但之前积累的大量教材,在当今的软硬件体系下,反而都成了负面教材.所以,有必要加强宣传,翻新大家的认知. 首先,天地倒悬,结论先行:当你需要并行时,优先考虑不需要线程间共享数据的设计,其次考虑共享Immutable的数据,最糟情况是共享Mutable数据.这个最糟选择,意味着最差的性能,最复杂啰嗦的代码逻辑,最容易出现难于重现的bug,以及不能测试预防的死锁可能性.在代码实现上,优先考虑高抽象级别的并行库(如C++11的future,PP

ActionScript3游戏中的图像编程(连载二十一)

1.4.3 用灰度/明度指导色彩搭配 试着只通过调整不等于0的通道,让上一排的色彩在灰度上保持一致,把三个颜色分别代入到心理学公式.得 Gray(red) = r * 0.299 Gray(green) = g * 0.587 Gray(blue) = b * 0.114 让它们灰度相等,则 r * 0.299 = g * 0.587 = b * 0.114 可见,b值一定最大,不妨让b取最大值255,求得r=97, g=50.把这两个值分别应用到红和绿两个色块上. 绿色不再显得刺眼,整体感也因

ActionScript3游戏中的图像编程(连载二)

内容编排 本套教程共分3部分. 第一部分介绍色彩的相关知识及Flash,Photoshop等可视化软件在图形图像处理方面的一些特性,包括常用的色彩模式,Photoshop图层效果,ActionScript滤镜,混合模式等知识,并指出这些特性在不同软件下的异同. 第二部分以应用数学为思想,以BitmapData,像素为工具,深入剖析各种图像处理技术的工作原理,让读者可以深入到算法的层面上认识图像处理,在ActionScript乃至其它支持像素级编程的语言中实现与Photoshop等专业图像软件相媲