從序列到並行,從並行到分散式
問題:序列(Sequential)、並行(parallel)、併發(Concurrent)和分散式(distributed)的區別是什麼?
1. 概念
假設有AB兩個任務,則序列、並行、併發的區別如圖1所示。
序列
A和B兩個任務執行在一個CPU執行緒上,在A任務執行完之前不可以執行B。即,在整個程式的執行過程中,僅存在一個執行上下文,即一個呼叫棧一個堆。程式會按順序執行每個指令。
並行
並行性指兩個或兩個以上事件或活動在同一時刻發生。在多道程式環境下,並行性使多個程式同一時刻可在不同CPU上同時執行。比如,A和B兩個任務可以同時執行在不同的CPU執行緒上,效率較高,但受限於CPU執行緒數,如果任務數量超過了CPU執行緒數,那麼每個執行緒上的任務仍然是順序執行的。
併發
併發指多個執行緒在巨集觀(相對於較長的時間區間而言)上表現為同時執行,而實際上是輪流穿插著執行,併發的實質是一個物理CPU在若干道程式之間多路複用,其目的是提高有限物理資源的執行效率。 併發與並行序列並不是互斥的概念,如果是在一個CPU執行緒上啟用併發,那麼自然就還是序列的,而如果在多個執行緒上啟用併發,那麼程式的執行就可以是既併發又並行的。

圖1 序列、並行、併發的區別
而分散式和並行的區別如下:
分散式
分散式在並行處理的基礎上,強調任務正在執行的物理裝置,如處理器、記憶體等等硬體,在物理上是分開的。而平行計算是指在一臺計算機上的計算,在物理上不分開。
2. 例子
假設有A,B兩個任務,任務A需要計算1-100000之間所有質數的和,任務B需要計算100001-200000之間所有質數的和。
則採用序列的方法設計的程式如下:
public class Main { //判斷是否為質數 private static boolean isPrime(int n) { if(n < 2) return false; if(n == 2) return true; if(n%2==0) return false; for(int i = 3; i < n; i += 2) if(n%i == 0) return false; return true; } //序列計算 private static void serial() { long time1 = System.currentTimeMillis(), time2,time3; long count = 0; for(int i=1;i<=100000;++i){ if(isPrime(i)) count+=i; } time2=System.currentTimeMillis(); System.out.println("1-100000之間質數和為"+count+" 耗時:"+(time2- time1) + "毫秒"); count = 0; for(int i=100001;i<=200000;++i){ if(isPrime(i)) count+=i; } time3 = System.currentTimeMillis(); System.out.println("100001-200000之間質數和為"+count+" 耗時:"+(time3 - time2) + "毫秒"); System.out.println("總耗時:"+ (time3 - time1) + "毫秒"); } //主函式 public static void main(String[] args) { serial(); } }
在序列計算的程式中,只有一個CPU執行緒,且該執行緒按順序執行AB兩個任務。程式執行結果如下:

序列計算的執行結果
採用併發的方法設計的程式如下:
public class Main{ private static boolean isPrime(int n) { if(n < 2) return false; if(n == 2) return true; if(n%2==0) return false; for(int i = 3; i < n; i += 2) if(n%i == 0) return false; return true; } public static void main(String[] args) { serialConcurrency(); } private static void serialConcurrency() { long time = System.currentTimeMillis(); //任務切換標識,1代表A任務,2代表B任務 int task = 1; //計數器 long count1 = 0, count2 = 0; int i=1,j=100001; while (true) { if(task == 1 && i++<=100000) { if(isPrime(i)) count1+=i; task = 2; } else if(task == 2 && j++<=200000) { if(isPrime(j)) count2+=j; task = 1; } else{ break; } } System.out.println("1-100000之間質數和為"+count1); System.out.println("100001-200000之間質數和為"+count2); System.out.println("總耗時:"+(System.currentTimeMillis() - time) + "毫秒"); } }
在併發計算的程式中,同樣只有一個CPU執行緒,但是該執行緒會在AB兩個任務之間進行切換,可以發現,併發計算的總耗時反而大於序列計算,這是因為CPU在任務切換過程中需要消耗一定時間。程式執行結果如下:

併發計算的執行結果
採用並行的方法設計的程式如下:
public class Main { public static boolean isPrime(int n) { if(n < 2) return false; if(n == 2) return true; if(n%2==0) return false; for(int i = 3; i < n; i += 2) if(n%i == 0) return false; return true; } public static void main(String[] args) throws InterruptedException { long time1 = System.currentTimeMillis(),time2; Task task1 = new Task(1,100000); Task task2 = new Task(100001,200000); Thread thread1 = new Thread(task1); Thread thread2 = new Thread(task2); thread1.start(); thread2.start(); while (thread1.isAlive() || thread2.isAlive()){ Thread.sleep(1); } time2 = System.currentTimeMillis(); System.out.println("總耗時:"+(time2 - time1)+"毫秒"); } } class Task implements Runnable{ private int start; private int end; Task(int start, int end) { this.start = start; this.end = end; } public void run() { long time = System.currentTimeMillis(); long count = 0; for(int i=start;i<=end;++i){ if(Main.isPrime(i)) count+=i; } System.out.println(String.format("%d-%d之間質數和為%d,耗時:%d毫秒",start,end,count,(System.currentTimeMillis()- time))); } }
在平行計算的程式中,AB任務各佔用一個CPU執行緒,AB任務同時執行,總共耗費的時間約等於AB任務的最長耗時,程式執行結果如下:

平行計算的執行結果
模式 | CPU執行緒數 | 總耗時 |
---|---|---|
序列 | 1 | 2736毫秒 |
併發 | 1 | 2933毫秒 |
並行 | 2 | 2277毫秒 |
3.總結
由上表可知並行的總耗時是最小的,效率最高(如果AB兩個任務耗時更接近,則平行計算的效率將更高)。但由於平行計算受限於CPU執行緒數,當計算量超出單臺計算機的計算能力時,人們就開始考慮使用多臺計算機同時處理一個任務,分散式計算應用而生。分散式計算將任務分解成許多小的部分,分配給多臺計算機進行處理,從而整體上節約了計算時間。Hadoop的MapReduce就是一種分散式計算框架,我們之後會對MapReduce進行詳細的探討。