研究遺傳算法的一大詬病就是每次運行程序的結果並不是完全一樣的,有時候能找到最優解有時候找不到最優解,這就是遺傳算法的概率性導致的。那么怎么評價你的方法的好壞吶,這時候就要多次獨立運行程序最后取結果的平均值或者計算算法的運行成功率。那么問題就來了,遺傳算法的運行時間本來就略長,尤其當測試數據集很大且數量很多的時候,做一次實驗跑完所有數據的時間有時候有點讓人難以接受。於是想到了使用多線程,這樣就可以同時運行多組獨立的實驗,最后再整合所有子線程的輸出數據,計算運行成功率或者取最優解的平均值即可,懷揣着這個想法,開始嘗試着用多線程編程來加快實驗進度。以下是這幾天的學習、實驗過程和結果。
本人一直用Windows下的VS 2010編程,看了很多關於多線程編程的博客和資料之后我決定不用win32的API來實現多線程(各種函數名太長看着都懶得搞),而使用pthread的windows開發包(下載地址http://sourceware.org/pthreads-win32/ 下載任意版本,本人下載的是最新版pthreads-w32-2-9-1-release.zip,在VS下的配置方法稍后描述)比較方便。So, 多線程編程即將搞起來~~~
第一步:在VS2010下配置pthread
下載好pthread之后解壓到任意文件夾,解壓之后包含三個文件夾 Pre-built.2 pthreads.2 以及QueueUserAPCEx。在配置中需要用到的只有第一個文件夾下的東西。打開Pre-built.2文件夾會開到三個文件夾分別是dll、include、lib以及一對其他文件,從這三個文件夾就可以看出接下來要怎么配置。
1.將include路徑填加為VS的包含路徑。 例如我的include路徑是F:\C&C++\多線程編程\pthreads-w32-2-9-1-release\Pre-built.2\include。填加該路徑至包含路徑
打開 屬性管理器->右擊->屬性->VC++目錄->包含目錄, 將要填加的目錄復制粘貼進去點擊確定(主意千萬不要在路徑前面填加“$”符號,否則出錯)
2.填加庫目錄。同第一步,將lib文件夾的目錄填加進庫目錄
3.填加lib文件到附加依賴項。屬性管理器->右擊->屬性->鏈接器->輸入->附加依賴項。將lib文件夾下的.lib文件文件名復制粘貼在該項目下,記得每一個lib文件名換行。
4.將dll文件夾下的dll文件復制粘貼到你的VS項目根目錄下。就可以開始使用pthread在VS中寫多線程程序了。
第二步.編程
下面是我的程序
#include"rcpsp.h"//自己編寫的頭文件 #include<pthread.h>//pthread頭文件,多線程的東西都在里面 //===========10個線程========================= //每個線程是一次獨立的實驗,每組數據十次獨立運行,所以編寫十個子線程,以求快速完成實驗 void* process_0(void *arg); void* process_1(void *arg); void* process_2(void *arg); void* process_3(void *arg); void* process_4(void *arg); void* process_5(void *arg); void* process_6(void *arg); void* process_7(void *arg); void* process_8(void *arg); void* process_9(void *arg); //========================================== typedef struct { //===========Input Variables============ .... //===========Output Variables=========== .... }InputData; int main(void) { pthread_t p0,p1,p2,p3,p4,p5,p6,p7,p8,p9;//定義pthread變量 InputData Data[10]; //子線程輸入輸出數據 //=================multi threads========================= pthread_create(&p0,NULL,process_0,(void*)&Data[0]); pthread_create(&p1,NULL,process_1,(void*)&Data[1]); pthread_create(&p2,NULL,process_2,(void*)&Data[2]); pthread_create(&p3,NULL,process_3,(void*)&Data[3]); pthread_create(&p4,NULL,process_4,(void*)&Data[4]); pthread_create(&p5,NULL,process_5,(void*)&Data[5]); pthread_create(&p6,NULL,process_6,(void*)&Data[6]); pthread_create(&p7,NULL,process_7,(void*)&Data[7]); pthread_create(&p8,NULL,process_8,(void*)&Data[8]); pthread_create(&p9,NULL,process_9,(void*)&Data[9]); pthread_join(p0,NULL); pthread_join(p1,NULL); pthread_join(p2,NULL); pthread_join(p3,NULL); pthread_join(p4,NULL); pthread_join(p5,NULL); pthread_join(p6,NULL); pthread_join(p7,NULL); pthread_join(p8,NULL); pthread_join(p9,NULL); //==================輸出數據后處理======================== .... }
以上是我主程序的大概流程,由於pthread_create()創建子線程的時候給子線程函數的傳入參數只有一個,所以當你需要給子函數傳遞多個函數參數或者需要傳出參數的時候只能定義一個結構體,將所有輸入和輸出數據全部包裝在結構體中,然后將結構體變量的引用傳入子線程函數即可。在本例中,將所有輸入輸出參數全部包裝在結構體InputData中,由於有十個子線程,為了避免不必要的麻煩,所以定義十個變量,用來輸入和輸出函數參數和返回值。
關於pthread_join( , )函數,它有兩個參數,具體含義可以查閱其他博文。其作用是讓主線程等待子線程結束,整個main函數是主線程,如果沒有pthread_join這句代碼,當主線程結束的時候,所有子線程也會被終止,而不管子線程是否已經結束,而大多數情況下,主線程會很快結束,然后創建的子線程還沒有執行就隨着主線程的結束而結束,所以一定要加一句等待子線程結束的代碼。當然還有更簡便的函數pthread_exit(),也可在所有線程的最后寫上phread_exit(0)來讓主線程等待所有子線程的結束。
phread.h中包含很多函數,在這里就不一一介紹,具體可參閱https://sourceware.org/pthreads-win32/manual/index.html
關於創建多少個線程才能效率更好的問題,網上和書上大部分推薦創建的線程數為CPU核數N 的2倍或者2*N+2個。所以這多線程和硬件關系特別大,如果是單核處理器,那完全不建議使用多線程,不然不但不會提高運行效率,反而線程之間的切換會占去很大一部分開銷。
那么,我的實驗運行速度提高了多少吶?? 答案是:沒有提高。
讓兩台機子分別跑同一個數據集,一個采用上面的多線程方法,另一個串行執行每一組實驗,經過對比,很悲傷的發現,串行的實驗組跑得更快,當然和遺傳算法本身也有很大的關系,每次一初始化不同,變異、交叉概率不同導致的找到解的時間就會不同,但是,這樣的結果也是夠讓我傷心的了,搞了兩天的多線程編程,查閱各種博客、去圖書館查資料,改程序,調程序,結果~~效果並不理想,也許是我的實驗方法有問題,亦或是我的程序有問題,反正,多線程並沒有加快我的實驗進度。但是以后還是會嘗試用多線程或者並行編程來加快遺傳算法的實驗速度的。