什麼叫做多線程編程,大概意思就是說對多個任務同時進行控制,而且相互之間還需要協調。相信很多人和我一樣在開始的時候,認為多線程編程是為了利用多核處理器,使程序運行的更快。但是,現在只要打開操作系統,那麼肯定就不止一個任務在工作,所以,和多核沒有什麼關系。加快速度,從某種層面上來說的確有這個效果。但是,其作用的方式是不同的,有點懵?OK,我們一會再說。
在項目中,遇到了不少需要用到多線程編程的例子。有的是在C++庫裡需要用到,有的是在Java語言用到,包括開發安卓的過程中。像是在QT中,用到的庫是QThread,在java中繼承Thread類。在這裡,我想用C++中的thread.h這個頭文件裡的函數來進行舉例,這個頭文件的方便移植且不需要增加其他的庫。雖然,我目前實在Raspbian上面進行編程,但是可以保證在Linux平台下也可以正常運行,windows下mingw也是可以正常運行的,其他的自己試一下吧。
現在,先舉幾個在項目中用到多線程編程的例子吧。
1.armLinux的開發中,經常使用中斷。(什麼叫中斷模式,百度百科:暫時停止當前程序的執行轉而執行處理新情況的程序和執行過程。簡單的來說:就是當運行到某處,暫停運行,直到從硬件處獲得某個信號,比如說獲得鍵盤某個鍵按下,以後有機會細講)這時候,我們不可以讓主進程暫停了,因為主進程還有其他操作。有時候,用到的中斷程序不止一個。那麼,就需要開多個線程每個中斷都處於不同的線程當中,才能檢測各個中斷信號。
2.在開發安卓的過程中,有一些操作是不允許在主線程中進行操作。例如網絡通信。其實也是可以理解的,網絡通信大多都需要長時間操作,那麼如果主線程已經在操作UI了,如果此時耗時太多在網絡通信上面的話,UI的創建就會延遲,這樣手機就會出現卡頓,我們的手機已經夠卡的了,所以,安卓就不允許在主線程中進行網絡通信。
創建兩個線程,輸出他們是第幾個線程。
#include// 線程相關類,在類Unix下一般都有 #include using namespace std; // 定義線程啟動的函數 void* fun1(void* args) { cout << "This is first Thread!\n"; } void* fun2(void* args) { cout << "This is second Thread!\n"; } int main() { // 線程結構體 pthread_t pt1,pt2; // 創建線程,線程創建成功,返回值是0 if(pthread_create(&pt1,NULL,fun1,NULL) != 0) { cout << "Error in create first Thread!\n"; } if(pthread_create(&pt2,NULL,fun2,NULL) != 0) { cout << "Error in create second Thread!\n"; } //等待線程結束後,結束進程 pthread_exit(NULL); return 0; }
注意:在這裡編譯的時候,需要在IDE中設置靜態編譯庫。所以,我就直接用命令行來進行編譯。
g++ -o thread0 thread0.cpp -lpthread ./thread0
大多數情況下會先輸出第一個線程所要輸出的內容,但是如果多次運行的話,會發現有時候線程二的內容會在線程一的內容出來前出現。OK,現在有三個問題了:
1.不是C++多線程編程嗎,那麼怎麼在類裡面使用呢?
2.怎麼往線程裡面傳入參數呢?
3.如果我們必須要線程一完成以後再進行線程二,怎麼辦?
如果線程調用類中的函數,那麼必須將該函數生命為靜態函數。
靜態成員函數屬於靜態全局區,線程可以共享該區域。
#include// 線程相關類,在類Unix下一般都有 #include using namespace std; // 定義一個類 class MyThread { private: public: // 定義線程啟動的函數,必須是靜態的 static void* fun1(void* args); static void* fun2(void* args); }; void* MyThread::fun1(void* args) { cout << "This is first Thread!\n"; } void* MyThread::fun2(void* args) { cout << "This is second Thread!\n"; } int main() { // 線程結構體 pthread_t pt1,pt2; // 創建線程,線程創建成功,返回值是0 if(pthread_create(&pt1,NULL,MyThread::fun1,NULL) != 0) { cout << "Error in create first Thread!\n"; } if(pthread_create(&pt2,NULL,MyThread::fun2,NULL) != 0) { cout << "Error in create second Thread!\n"; } //等待線程結束後,結束進程 pthread_exit(NULL); return 0; }
命令其實和上面一樣的:
g++ -o thread1 thread1.cpp -lpthread ./thread1
現在,往裡面傳入參數來確定分別是哪個線程來調用的。
#include// 線程相關類,在類Unix下一般都有 #include #include using namespace std; // 定義一個類 class MyThread { public: // 定義線程啟動的函數,必須是靜態的 static void* fun(void* args); }; void* MyThread::fun(void* args) { cout << "This is " << *((string*) args) << " Thread!\n"; } int main() { // 線程結構體 pthread_t pt; string ss1,ss2; // 創建線程,線程創建成功,返回值是0 ss1 = "first"; if(pthread_create(&pt,NULL,MyThread::fun,(void*)&ss1) != 0) { cout << "Error in create first Thread!\n"; } ss2 = "second"; if(pthread_create(&pt,NULL,MyThread::fun,(void*)&ss2) != 0) { cout << "Error in create first Thread!\n"; } //等待線程結束後,結束進程 pthread_exit(NULL); return 0; }
編譯運行命令以後不寫了,同上即可。
我們來看一下結果,結果有點奇怪。有時候是first在前,有時候second在前,這自然不用多說。但是還有一種情況是:段錯誤。所以,我們繼續來控制一下每個線程運行的前後順序。
定義兩個線程,兩個線程啟動,在第一次輸入後,結束地一個線程,在第二次輸入後,開始運行線程二中的實際內容。
#include#include #include using namespace std; // 定義第一個線程類 class MyThread0 { public: static bool run; // 定義線程啟動的函數,必須是靜態的 static void* fun(void* args); }; // 定義第二個線程類 class MyThread1 { public: static bool waitRun; // 定義線程啟動的函數,必須是靜態的 static void* fun(void* args); }; bool MyThread0::run = true; bool MyThread1::waitRun = true; void* MyThread0::fun(void* args) { int i = 0; while(run) { i = 1; // 執行的任務 } cout << i << endl; } void* MyThread1::fun(void* args) { while(waitRun); // 實際執行的任務 cout << "MyThread1 is OK!" << endl; } int main() { pthread_t pt; if(pthread_create(&pt,NULL,MyThread0::fun,NULL) != 0) { cout << "Error in create first Thread!\n"; } if(pthread_create(&pt,NULL,MyThread1::fun,NULL) != 0) { cout << "Error in create first Thread!\n"; } // 至此兩個程序已經開始運行 getchar(); MyThread0::run = false; getchar(); MyThread1::waitRun = false; pthread_exit(NULL); return 0; }
我用這個方法很長時間了,殺人越貨之利器,好理解,沒有額外的函數,而且無論語言都可以直接移植。但是畢竟不是個特別好的辦法,而且如果兩個線程共享一個數據,那麼就一定要先鎖定某個線程,然後准備好資源後才可以啟動另外一個線程,太過麻煩。而且一直要陷在死循環裡暫停程序也不像個事。(其實,我是從嵌入式裡面學到的)其實,在學習操作系統/數據庫原理中對於並發性操作,都是相當需要注意的,每個線程的先後都要控制好,而且需要注意數據的保護和讀寫,這時候我們就需要引入互斥鎖機制。
我們首先來看一下程序,程序中最後有個函數pthread_exit,這裡的意思是等所有子線程結束後,主線程結束,也就是說到這個函數程序將會結束,以後所有的語句都不會再執行。而且,還有個問題就是等待所有線程結束,根本無法判斷個別線程結束。那麼,我們可不可以先等待某個線程結束,再繼續運行下一步呢?
#include#include #include using namespace std; void* change_value(void* args) { cout << "This thread value is " << *((int *)args) << endl; } int main() { int value1 = 1,value2 = 2; pthread_t pt1,pt2; int ret = pthread_create(&pt1, NULL, change_value, (void*)&value1); if(ret != 0) { cout << "pthread_create error:error_code=" << ret << endl; } //pthread_join的另外一個作用就是可以控制等待某個進程結束 pthread_join(pt1,NULL); ret = pthread_create(&pt2, NULL, change_value, (void*)&value2); if(ret != 0) { cout << "pthread_create error:error_code=" << ret << endl; } pthread_join(pt2,NULL); return 0; }
我們首先來看一下程序,程序中最後有個函數pthread_exit,這裡的意思是等子線程結束後,主線程結束,也就是說到這個函數程序將會結束,以後所有的語句都不會再執行。OK,那麼我們即使獲得線程結束信息,也無法表示。那麼我們吧這條語句去了,去了的話會發現有的線程還沒有運行完成,程序就已經結束了,怎麼解決?當然也可以估計一下時間然後等待,或者設置一個公共變量,然後在主線程中利用循環暫停,但是,我們這裡用系統提供給我們的函數來進行操作。
#include#include #include using namespace std; void* change_value(void* args) { cout << "This thread value is " << *((int *)args) << endl; int result = *((int *)args) + 10; pthread_exit((void*)result); } int main() { int value1 = 1,value2 = 3; void* result; pthread_t pt1,pt2; int ret = pthread_create(&pt1, NULL, change_value, (void*)&value1); if(ret != 0) { cout << "pthread_create error:error_code=" << ret << endl; } pthread_join(pt1,&result); cout << "The thread1's result is " << (int)result << endl; ret = pthread_create(&pt2, NULL, change_value, (void*)&value2); if(ret != 0) { cout << "pthread_create error:error_code=" << ret << endl; } pthread_join(pt2,&result); cout << "The thread2's result is " << (int)result << endl; return 0; }
互斥鎖的作用是訪問數據時可以保證是被一個線程所訪問,並不能控制線程執行順序的先後。
#include#include using namespace std; // 定義第一個線程類 class MyThread { public: static int flag; // 定義線程啟動的函數,必須是靜態的 static void* fun1(void* args); static void* fun2(void* args); }; int MyThread::flag = 0; pthread_mutex_t sum_mutex; int temp; void* MyThread::fun1(void* args) { // 上鎖 pthread_mutex_lock(&sum_mutex); cout << "This is " << ++flag << " Thread1!\n"; // 解鎖 pthread_mutex_unlock(&sum_mutex); } void* MyThread::fun2(void* args) { pthread_mutex_lock(&sum_mutex); cout << "This is " << ++flag << " Thread2!\n"; pthread_mutex_unlock(&sum_mutex); } int main() { pthread_t pt; if(pthread_create(&pt,NULL,MyThread::fun1,NULL) != 0) { cout << "Error in create first Thread!\n"; } if(pthread_create(&pt,NULL,MyThread::fun2,NULL) != 0) { cout << "Error in create first Thread!\n"; } pthread_exit(NULL); cout << temp << endl; pthread_mutex_destroy(&sum_mutex); return 0; }
基本到此為止,多線程的一些最基本的使用就到此結束了,在使用的過程中,發現自己的很多基礎知識都有很多斷層,大概這就是我用到哪裡學到哪裡的一個大BUG。這樣也好,從最基本的開始一點一點來檢查。