1. 程式人生 > >淘寶面試題:如何充分利用多核CPU,計算很大的List中所有整數的和

淘寶面試題:如何充分利用多核CPU,計算很大的List中所有整數的和


引用 前幾天在網上看到一個淘寶的面試題:有一個很大的整數list,需要求這個list中所有整數的和,寫一個可以充分利用多核CPU的程式碼,來計算結果。
一:分析題目
從題中可以看到“很大的List”以及“充分利用多核CPU”,這就已經充分告訴我們要採用多執行緒(任務)進行編寫。具體怎麼做呢?大概的思路就是分割List,每一小塊的List採用一個執行緒(任務)進行計算其和,最後等待所有的執行緒(任務)都執行完後就可得到這個“很大的List”中所有整數的和。 
二:具體分析和技術方案
既然我們已經決定採用多執行緒(任務),並且還要分割List,每一小塊的List採用一個執行緒(任務)進行計算其和,那麼我們必須要等待所有的執行緒(任務)完成之後才能得到正確的結果,那麼怎麼才能保證“等待所有的執行緒(任務)完成之後輸出結果呢”?這就要靠java.util.concurrent包中的CyclicBarrier類了。它是一個同步輔助類,它允許一組執行緒(任務)互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的執行緒(任務)的程式中,這些執行緒(任務)必須不時地互相等待,此時 CyclicBarrier 很有用。簡單的概括其適應場景就是:當一組執行緒(任務)併發的執行一件工作的時候,必須等待所有的執行緒(任務)都完成時才能進行下一個步驟。具體技術方案步驟如下: 

  • 分割List,根據採用的執行緒(任務)數平均分配,即list.size()/threadCounts。
  • 定義一個記錄“很大List”中所有整數和的變數sum,採用一個執行緒(任務)處理一個分割後的子List,計運算元List中所有整數和(subSum),然後把和(subSum)累加到sum上。
  • 等待所有執行緒(任務)完成後輸出總和(sum)的值。

示意圖如下: 

三:詳細編碼實現
程式碼中有很詳細的註釋,這裡就不解釋了。 
Java程式碼  收藏程式碼
  1. /** 
  2.  * 計算List中所有整數的和<br> 
  3.  * 採用多執行緒,分割List計算 
  4.  * @author 飛雪無情 
  5.  * @since 2010-7-12
     
  6.  */  
  7. public class CountListIntegerSum {  
  8.     private long sum;//存放整數的和  
  9.     private CyclicBarrier barrier;//障柵集合點(同步器)  
  10.     private List<Integer> list;//整數集合List  
  11.     private int threadCounts;//使用的執行緒數  
  12.     public CountListIntegerSum(List<Integer> list,int threadCounts) {  
  13.         this.list=list;  
  14.         this.threadCounts=threadCounts;  
  15.     }  
  16.     /** 
  17.      * 獲取List中所有整數的和 
  18.      * @return 
  19.      */  
  20.     public long getIntegerSum(){  
  21.         ExecutorService exec=Executors.newFixedThreadPool(threadCounts);  
  22.         int len=list.size()/threadCounts;//平均分割List  
  23.         //List中的數量沒有執行緒數多(很少存在)  
  24.         if(len==0){  
  25.             threadCounts=list.size();//採用一個執行緒處理List中的一個元素  
  26.             len=list.size()/threadCounts;//重新平均分割List  
  27.         }  
  28.         barrier=new CyclicBarrier(threadCounts+1);  
  29.         for(int i=0;i<threadCounts;i++){  
  30.             //建立執行緒任務  
  31.             if(i==threadCounts-1){//最後一個執行緒承擔剩下的所有元素的計算  
  32.                 exec.execute(new SubIntegerSumTask(list.subList(i*len,list.size())));  
  33.             }else{  
  34.                 exec.execute(new SubIntegerSumTask(list.subList(i*len, len*(i+1)>list.size()?list.size():len*(i+1))));  
  35.             }  
  36.         }  
  37.         try {  
  38.             barrier.await();//關鍵,使該執行緒在障柵處等待,直到所有的執行緒都到達障柵處  
  39.         } catch (InterruptedException e) {  
  40.             System.out.println(Thread.currentThread().getName()+":Interrupted");  
  41.         } catch (BrokenBarrierException e) {  
  42.             System.out.println(Thread.currentThread().getName()+":BrokenBarrier");  
  43.         }  
  44.         exec.shutdown();  
  45.         return sum;  
  46.     }  
  47.     /** 
  48.      * 分割計算List整數和的執行緒任務 
  49.      * @author lishuai 
  50.      * 
  51.      */  
  52.     public class SubIntegerSumTask implements Runnable{  
  53.         private List<Integer> subList;  
  54.         public SubIntegerSumTask(List<Integer> subList) {  
  55.             this.subList=subList;  
  56.         }  
  57.         public void run() {  
  58.             long subSum=0L;  
  59.             for (Integer i : subList) {  
  60.                 subSum += i;  
  61.             }    
  62.             synchronized(CountListIntegerSum.this){//在CountListIntegerSum物件上同步  
  63.                 sum+=subSum;  
  64.             }  
  65.             try {  
  66.                 barrier.await();//關鍵,使該執行緒在障柵處等待,直到所有的執行緒都到達障柵處  
  67.             } catch (InterruptedException e) {  
  68.                 System.out.println(Thread.currentThread().getName()+":Interrupted");  
  69.             } catch (BrokenBarrierException e) {  
  70.                 System.out.println(Thread.currentThread().getName()+":BrokenBarrier");  
  71.             }  
  72.             System.out.println("分配給執行緒:"+Thread.currentThread().getName()+"那一部分List的整數和為:\tSubSum:"+subSum);  
  73.         }  
  74.     }  
  75. }  

有人可能對barrier=new CyclicBarrier(threadCounts+1);//建立的執行緒數和主執行緒main有點不解,不是採用的執行緒(任務)數是threadCounts個嗎?怎麼為CyclicBarrier設定的給定數量的執行緒參與者比我們要採用的執行緒數多一個呢?答案就是這個多出來的一個用於控制main主執行緒的,主執行緒也要等待,它要等待其他所有的執行緒完成才能輸出sum值,這樣才能保證sum值的正確性,如果main不等待的話,那麼結果將是不可預料的。 
Java程式碼  收藏程式碼
  1. /** 
  2.  * 計算List中所有整數的和測試類 
  3.  * @author 飛雪無情 
  4.  * @since 2010-7-12 
  5.  */  
  6. public class CountListIntegerSumMain {  
  7.     /** 
  8.      * @param args 
  9.      */  
  10.     public static void main(String[] args) {  
  11.         List<Integer> list = new ArrayList<Integer>();  
  12.         int threadCounts = 10;//採用的執行緒數  
  13.         //生成的List資料  
  14.         for (int i = 1; i <= 1000000; i++) {  
  15.             list.add(i);  
  16.         }  
  17.         CountListIntegerSum countListIntegerSum=new CountListIntegerSum(list,threadCounts);  
  18.         long sum=countListIntegerSum.getIntegerSum();  
  19.         System.out.println("List中所有整數的和為:"+sum);  
  20.     }  
  21. }  

四:總結
本文主要通過一個淘寶的面試題為引子,介紹了併發的一點小知識,主要是介紹通過CyclicBarrier同步輔助器輔助多個併發任務共同完成一件工作。Java SE5的java.util.concurrent引入了大量的設計來解決併發問題,使用它們有助於我們編寫更加簡單而健壯的併發程式。 

附mathfox提到的ExecutorService.invokeAll()方法的實現
這個不用自己控制等待,invokeAll執行給定的任務,當所有任務完成時,返回保持任務狀態和結果的 Future 列表。sdh5724也說用了同步,效能不好。這個去掉了同步,根據返回結果的 Future 列表相加就得到總和了。 Java程式碼  收藏程式碼
  1. /** 
  2.  * 使用ExecutorService的invokeAll方法計算 
  3.  * @author 飛雪無情 
  4.  * 
  5.  */  
  6. public class CountSumWithCallable {  
  7.     /** 
  8.      * @param args 
  9.      * @throws InterruptedException  
  10.      * @throws ExecutionException  
  11.      */  
  12.     public static void main(String[] args) throws InterruptedException, ExecutionException {  
  13.         int threadCounts =19;//使用的執行緒數  
  14.         long sum=0;  
  15.         ExecutorService exec=Executors.newFixedThreadPool(threadCounts);  
  16.         List<Callable<Long>> callList=new ArrayList<Callable<Long>>();  
  17.         //生成很大的List  
  18.         List<Integer> list = new ArrayList<Integer>();  
  19.         for (int i = 0; i <= 1000000; i++) {  
  20.             list.add(i);  
  21.         }  
  22.         int len=list.size()/threadCounts;//平均分割List  
  23.         //List中的數量沒有執行緒數多(很少存在)  
  24.         if(len==0){  
  25.             threadCounts=list.size();//採用一個執行緒處理List中的一個元素  
  26.             len=list.size()/threadCounts;//重新平均分割List  
  27.         }  
  28.         for(int i=0;i<threadCounts;i++){  
  29.             final List<Integer> subList;  
  30.             if(i==threadCounts-1){  
  31.                 subList=list.subList(i*len,list.size());  
  32.             }else{  
  33.                 subList=list.subList(i*len, len*(i+1)>list.size()?list.size():len*(i+1));  
  34.             }  
  35.             //採用匿名內部類實現  
  36.             callList.add(new Callable<Long>(){  
  37.                 public Long call() throws Exception {  
  38.                     long subSum=0L;  
  39.                     for(Integer i:subList){  
  40.                         subSum+=i;  
  41.                     }  
  42.                     System.out.println("分配給執行緒:"+Thread.currentThread().getName()+"那一部分List的整數和為:\tSubSum:"+subSum);  
  43.                     return subSum;  
  44.                 }  
  45.             });  
  46.         }  
  47.         List<Future<Long>> futureList=exec.invokeAll(callList);  
  48.         for(Future<Long> future:futureList){  
  49.             sum+=future.get();  
  50.         }  
  51.         exec.shutdown();  
  52.         System.out.println(sum);  
  53.     }  
  54. }  

一些感言
這篇文章是昨天夜裡11點多寫好的,我當時是在網上看到了這個題目,就做了一下分析,寫了實現程式碼,由於水平有限,難免有bug,這裡感謝xifo等人的指正。這些帖子從發表到現在不到24小時的時間裡創造了近9000的瀏覽次數,回覆近100,這是我沒有想到的,javaeye很久沒這麼瘋狂過啦。這不是因為我的演算法多好,而是因為這個題目、這篇帖子所體現出的意義。大家在看完這篇帖子後不光指正錯誤,還對方案進行了改進,關鍵是思考,人的思維是無窮的,只要我們善於發掘,善於思考,總能想出一些意想不到的方案。 

從演算法看,或者從題目場景對比程式碼實現來看,或許不是一篇很好的帖子,但是我說這篇帖子是很有意義的,方案也是在很多場景適用,有時我們可以假設這不是計算和,而是把資料寫到一個個的小檔案裡,或者是分割進行網路傳輸等等,都有一定的啟發,特別是回帖中的討論。 

單說一下回帖,我建議進來的人儘量看完所有的回帖,因為這裡是很多人集思廣益的精華,這裡有他們分析問題,解決問題的思路,還有每個人提到的解決方案,想想為什麼能用?為什麼不能用?為什麼好?為什麼不好?


我一直相信:討論是解決問題、提高水平的最佳方式!

相關推薦

試題如何充分利用CPU計算List所有整數

引用 前幾天在網上看到一個淘寶的面試題:有一個很大的整數list,需要求這個list中所有整數的和,寫一個可以充分利用多核CPU的程式碼,來計算結果。 一:分析題目從題中可以看到“很大的List”以及“充分利用多核CPU”,這就已經充分告訴我們要採用多執行緒(任務)進行

【好文】試題如何充分利用CPU計算List所有整數

引用 前幾天在網上看到一個淘寶的面試題:有一個很大的整數list,需要求這個list中所有整數的和,寫一個可以充分利用多核CPU的程式碼,來計算結果。 一:分析題目 從題中可以看到“很大的List”以及“充分利用多核CPU”,這就已經充分告訴我們要採用多執行緒(任務)進行

如何充分利用CPU計算List所有整數

引用 前幾天在網上看到一個淘寶的面試題:有一個很大的整數list,需要求這個list中所有整數的和,寫一個可以充分利用多核CPU的程式碼,來計算結果。 一:分析題目從題中可以看到“很大的List”以及“充分利用多核CPU”,這就已經充分告訴我們要採用多執行緒(任務)進行

11. 微軟試題輸入一個單向連結串列輸出該連結串列倒數第k個結點。連結串列的倒數第0個結點為連結串列的尾指標

題目:輸入一個單向連結串列,輸出該連結串列中倒數第k個結點。連結串列的倒數第0個結點為連結串列的尾指標。 分析: 單鏈表只能向後遍歷,不能向前遍歷,尾指標好找,倒數第K個不能從尾指標向前找。 倒的不好找,正的好找,我們只需要知道連結串列的總長度,就可以知道正數第幾個節點(

為什麽python的線程不能利用CPU但是咱們在寫代碼的時候線程的確是在並發而且還比單線程快。

全局 睡眠 read 處理 sleep roc 需要 寫代碼 強制 python裏的多線程是單cpu意義上的多線程,它和多cpu上的多線程有著本質的區別。單cpu多線程:並發多cpu多線程:並行內部包含並發 首先強調背景: 1、GIL是什麽?GIL的全稱是Gl

試題常見的執行緒實戰手撕程式碼(順序列印數字、字母)

問題一:   一個多執行緒的問題,用三個執行緒,順序列印字母A-Z,輸出結果是1A 2B 3C 1D 2E…列印完畢最後輸出一個Ok。 程式碼一: public class forCharacter { private static char c = 'A';

支付試題 30秒內限制某方法被呼叫100次 (一個時間段限制方法被呼叫的次數)

假設一個Class的方法 a,被限定30秒內只能被呼叫100次,如何實現。 當時沒答上,回來補上    1、定義呼叫介面Icallee public interface Icallee {public void play(); } 2、實現呼叫介面類 publi

go語言試題輸入一段英文字串找出重複出現次數最的字母

package main import ( "bufio" "os" "fmt" "strings" ) func main() { reader := bufio.NewReader(os.Stdin) str, err := reader.

python學習筆記- day10-【問題 python為什麽python的線程不能利用CPU?】

例如 currency 視頻 stat 解碼 核心數 __name__ args 制作 為什麽python的多線程不能利用多核CPU,但是咱們在寫代碼的時候,多線程的確是在並發,而且還比單線程快。 一、python的多線程不能利用多核CPU? 原因: 因為GIL,

Hive試題hive有哪些udf函式作用

UDF(user-defined function)作用於單個數據行,產生一個數據行作為輸出。(數學函式,字串函式) UDAF(使用者定義聚集函式 User- Defined Aggregation Funcation):接收多個輸入資料行,併產生一個輸出資料行。(count,max)

阿里試題FileInputStream 在使用完以後不關閉流想二次使用可以怎麼操作

FileInputStream 中有一個方法是open 方法呼叫的是本地的開啟檔案的方法,fileinputStream 就是通過這個方法來開啟檔案的,所以如果要重寫讀取這個檔案,不重新建立物件,那麼只要呼叫這個方法就可以了。 /** * Opens the specifie

【轉載】試題“你能不能談談java GC是在什麼時候對什麼東西做了什麼事情?”

面試題目:  地球人都知道,Java有個東西叫垃圾收集器,它讓建立的物件不需要像c/cpp那樣delete、free掉,你能不能談談: GC是在什麼時候,對什麼東西,做了什麼事情?   以上算是三個問題,下面逐一分析: 問題一回答:什麼時候? 1.系統空閒的時候。

試題C++有了malloc/free為什麼還需要new、delete?

1、面試寶典面試題(P81):C++有了malloc/free,為什麼還需要new、delete? malloc與free是C、C++語言的標準庫函式,new/delete是C++的運算子。他們都用於申請動態記憶體和釋放記憶體。 對於非內部資料型別的物件而言,只用mall

試題“你能不能談談java GC是在什麼時候對什麼東西做了什麼事情?”

地球人都知道,Java有個東西叫垃圾收集器,它讓建立的物件不需要像c/cpp那樣delete、free掉,你能不能談談,GC是在什麼時候,對什麼東西,做了什麼事情?一.回答:什麼時候?1.系統空閒的時候。    分析:這種回答大約佔30%,遇到的話一般我就會準備轉向別的話題,譬如演算法、譬如SSH看看能否發掘

一道試題請寫sql查詢出成績小於60的同學的姓名和平均分並按平均分排序

給出如下3張表,stu表、sc表和course表: /* Navicat MySQL Data Transfer Source Server         : db_fightLandlor Source Server Version : 50520 Source Ho

某團試題JVM 堆記憶體溢位後其他執行緒是否可繼續工作?

轉載註明:http://dwz.win/gHc 最近網上出現一個美團面試題:“一個執行緒OOM後,其他執行緒還能執行嗎?”。我看網上出現了很多不靠譜的答案。這道題其實很有難度,涉及的知識點有jvm記憶體分配、作用域、gc等,不是簡單的是與否的問題。 由於題目中給出的OOM,java中OOM又分很多型別;比如:

曹工說試題一個執行緒協同問題解法繁多都要被玩壞了趁著沒壞一起玩吧

# 前言 最近兩個月寫文章很少,因為自己學習狀態也不是很好,我看了下,上一篇文章,都是一個月前了。 不知道大家有沒有感覺,小學初中讀的一些書,看的一些文章,到現在都印象深刻,反倒是高中學的知識,高考後就慢慢消散,直到遺忘。 我想說的是,記得初中學過魯迅的《藤野先生》,裡面有一段話,大意是:久了不聯絡,有

python的線程為什麽不能利用CPU

虛擬 pytho 能夠 並發 我們 就是 比較 inter 情況 python 為什麽不能利用多核CPU GIL: (1)其實是因為在python中有一個GIL(Global Interpreter Lock),中文為:全局解釋器鎖。 1、是最開始python為了數據安全設

python單程序能否利用cpu的測試結論

在很早的時候,就聽網上的文章說: python有GIL,所以在單程序內,即使使用多執行緒也無法利用到多核的優勢,同一時刻,python的位元組碼只會執行在一個cpu上。 以前也是奉為真理,直到今天在對自己的python server做效能測試的時候,發現一個python程序的c

如何利用CPU來加速你的Linux命令 — awk, sed, bzip2, grep, wc等

你是否曾經有過要計算一個非常大的資料(幾百GB)的需求?或在裡面搜尋,或其它操作——一些無法並行的操作。資料專家們,我是在對你們說。你可能有一個4核或更多核的CPU,但我們合適的工具,例如 grep, bzip2, wc, awk, sed等等,都是單執行緒的,只能使