一道面試題:用多線程求1000以內的素數有多少個?並給出消耗時間


     我曾經去一個公司面試,遇到這么一個題目:求1000以內的素數有多少個?用多線程實現,並給出消耗時間。我想了半天,沒有想出多線程的解決方案。今天因為機緣到了,我淺談下我的解法。

     這道題,顯然得考慮兩個問題:

     1、多線程的問題

     2、算法性能問題

     有人覺得1000以內還考慮什么算法性能?這肯定很快。但是話說回來,這個都有必要用多線程嗎?如果我們求10000000以內的素數有多少個?是不是必須考慮以上兩個問題了?多線程和算法優化的目的都是為了提高程序執行的效率。我們首先來考慮算法問題,什么是素數?素數:也稱為質數,在自然數中除了1和本身能整除外,沒有其它數可以整除,並且1不是素數。比如2,3都是素數。好了,我們看代碼:

 1           private static bool IsSushu1(int n)
 2             {
 3                 bool isSuFlag = true;
 4                 if (n <= 1) return false;
 5 
 6                 for (int i = 2; i < n; i++)
 7                 {
 8                     if (n % i == 0)
 9                     {
10                         isSuFlag = false;
11                         break;
12                     }
13                 }
14                 return isSuFlag;
15             }

這是求一個數是不是素數的算法,此算法根據定義而來,如果面臨千萬級的數據,直接出不來結果。我們能不能優化下?答案是肯定的,所以數學學得好的話,對計算機工作者來說,總是有益無害,我們來看改進后的代碼:

 1            private static bool IsSuShu(int n)
 2             {
 3                 bool isSuFlag = true;
 4                 if (n <= 1) return false;
 5 
 6 
 7                 for (int i = 2; i <= (int)Math.Sqrt((double)n); i++)
 8                 {
 9                     if (n % i == 0)
10                     {
11                         isSuFlag = false;
12                         break;
13                     }
14                 }
15                 return isSuFlag;
16             }

代碼沒有大的改變,只是循環的次數大規模降低了,想一想,如果是百萬級的數據,開個平方根,那就成了1000級別。這個算法的原理,還沒有想明白,是從網上看到的,總之是實實在在的數學問題,如果數學思維很好的話,這個估計能想得來。

算法有了,那用多線程有哪些問題呢?

首先,我們每個線程的執行結果,必須拿到。其次,要算總的消耗時間。創建一個線程,我們可以直接用Thread創建,但是Thread創建比較消耗性能,而且要拿執行結果,也沒法拿。如果用ThreadPool 創建后台線程的話,倒是性能提高了,還是直接拿不到線程執行的結果。我於是想到了c#中的Task。task知識點比較多,可以慢慢學習。先看實現:

 1             private static void MultiThreadCompute(int n,int pageSize)
 2             {
 3                 List<Task<int>> tasks = new List<Task<int>>();
 4 
 5                 var start = DateTime.Now;
 6 
 7                 for (int i = 1; i <= n / pageSize; i++)
 8                 {
 9                     var pageIndex = i;
10                     int startNum = (pageIndex - 1) * pageSize;
11                     int endNum = startNum + pageSize;
12 
13                     int[] numbers = { startNum, endNum };
14 
15                     tasks.Add(Task.Factory.StartNew((obj) =>
16                      {
17                          int totalCount = 0;
18 
19                          int[] temps = obj as int[];
20 
21                          for (int j = temps[0]; j < temps[1]; j++)
22                          {
23                              if (IsSushu1(j))
24                              {
25                                  totalCount++;
26                              }
27                          }
28                          return totalCount;
29                      }, numbers));
30                 }
31 
32                 Task.Factory.ContinueWhenAll(tasks.ToArray(), (taskList) =>
33                 {
34                     var end = DateTime.Now;
35                     int result = 0;
36 
37                     foreach (var item in tasks)
38                     {
39                         result += item.Result;
40 
41                         Console.WriteLine("task{0}找到{1}個素數", item.Id, item.Result);
42                     }
43 
44                     Console.WriteLine("{0}以內找到{1}個素數,總花費時間:{2}", n, result, end.Subtract(start).TotalSeconds);
45 
46                 });

解釋下代碼,pageSize就是我借鑒了網頁上分頁的算法,每個線程執行一個頁的數據。從32行開始,這個是等所有的任務執行完畢,然后循環任務取得結果,也是異步調用,不會阻塞主程序的繼續執行。這兒也可以改成同步調用:

 1               Task.WaitAll(tasks.ToArray());
 2 
 3                 int result = 0;
 4 
 5                 foreach (var item in tasks)
 6                 {
 7                     result += item.Result;
 8 
 9                     Console.WriteLine("task{0}找到{1}個素數", item.Id, item.Result);
10                 }
11 
12                 Console.WriteLine("{0}以內找到{1}個素數", n, result);

Task的WaitAll方法,阻塞主線程,等所有的任務完成后,主線程開始統計結果。

上圖第一行是單線程執行的結果,用時1.7s,下面是多線程執行的結果,因為是異步調用,獲取任務執行的結果,所以主線程先輸出了“我是異步調用”,其實這行代碼在最后一行。

這次是同步調用,多線程花費的時間比異步調用多了0.1s,是不是偶然呢?我連續運行了多次,都在0.8s的級別上,而異步調用在0.7s的級別上。看來的確是快了一點。這兩次的運行結果,都是基於素數定義的算法,那我把改良后的算法用上,看是什么情況。

大家有沒有注意,此時多線程的優勢並沒有發揮出來,它和單線程都是0.02s,那么我們把數據量加到百萬級,看看結果:

僅僅拉開了0.4s差距,我們把數據調到千萬級別上,再看結果:

此時,多線程雖然比單線程節省了一半時間,但是還是有6s,增加線程數:

我增加這么多線程,才提高了將近2s,付出與收獲不成正比啊。事實證明,線程不一定越多越好,可以計算出一個合適的線程數,這樣才能花比較少的資源,盡最大努力提高性能。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM