C++11 ,封裝了thread的多線程的類,這樣對多線程的使用更加方便。
多線程的原理我不加贅述,可以參看操作系統等參考書。
多線程代碼可以最大化利用計算機性能資源,提高代碼的運行效率,是常用優化方法。
我不是C++大神,初學階段的菜鳥而已,很多問題我還是不理解當中的原理,寫這篇博客的原因,也是記錄自己的學習心得和思路,供自己日后自己思考。
首先從簡單的問題入手,如何寫一個多線程的C++代碼?
#include<iostream> #include<thread> void fun(int a){ a++; } int main(){ int a=0; std::thread t(fun,a); //創建一個線程t,t調用函數fun,a作為fun的參數,也要寫到thread的構造函數當中;
t.join(); //啟動線程t,並且阻塞主線程,等到線程t運行結束后,再繼續運行主線程; std::cout<<a<<std::endl; }
上面這段代碼是最簡單的多線程代碼,調用thread類,並利用了thread的構造函數創建一個線程t,thread類的構造函數重載了很多,后面會繼續說到。
在這里要說一下,thread類當中的兩個成員函數,join()和detach()。這兩個成員的作用就像上面代碼的注釋那樣,啟動新生成的線程的,但是區別在於join()函數是啟動子線程而阻塞主線程,當子線程運行結束后,才會繼續運行主線程。相比之下,detach()函數的作用是啟動子線程,並且讓子線程和主線程分離,子線程和主線程各運行各的,雖然兩個線程會因為共享內存池的原因在操作系統的層面發生發生阻塞等關系,但是在代碼層次上,兩個線程並不存在誰阻塞誰,很可能主線程已經運行結束了,子線程還在運行。
接下來,我們要說一下類當中的成員函數如何初始化thread類的構造函數。
對於類的成員函數,我們需要給出類對象的地址:
#include<iostream> #include<thread> class A{ public: void fun(int a,int b){ std::cout<<"this is A thread!"<<a<<std::endl; } }; int main(){ int k=0; A a; std::thread t(&A::fun,a,k,k+1); t.join(); }
std::thread t(&A::fun,a,k,k+1); 這個地方就可以看出thread類的構造對於成員函數的重載了,std::thread t(函數(成員函數)地址,對象地址,成員函數的參數1,參數2,參數3...)。
相比非成員函數,成員函數需要給出類實例化對象的地址,如果該線程是在同一類的某一成員函數當中被構造,則直接用this關鍵字代替即可。
其實,我在寫成員函數的多線程代碼的時候,發現成員函數的需要傳遞的參數太多會使thread類的構造函數重載失敗,我測試了一下,成員函數最多只能傳遞4個參數,也就說std::thread類的構造函數最多只能重載6個參數。
這一點,我並沒有找到相關文檔得到證實,只是在寫代碼的時候發現成員函數傳遞參數太多,會一直編譯不通過,偶然間發現這個點的,具體到底對不對,我也不是很確定。
其次,我們要說一下加鎖和解鎖的問題。
因為我們創造的每一個線程只要在一個進程內,都是共享內存池的,這樣在讀寫數據可能會發生混亂。
C++11提供了mutex類進行加鎖和解鎖。
#include<iostream> #include<thread> #include<mutex> std::mutex mut; class A{ public: volatile int temp; A(){ temp=0; } void fun(int num){ int count=10; while(count>0){ mut.lock(); temp++; std::cout<<"thread_"<<num<<"...temp="<<temp<<std::endl; mut.unlock(); count--; } } void thread_run(){ std::thread t1(&A::fun,this,1); std::thread t2(&A::fun,this,2); t1.join(); t2.join(); } }; int main(){ A a; a.thread_run(); }
然后,我們說一下volatile關鍵字。
volatile和const關鍵很相似,都是修飾變量的,只是二者功能不一樣。
volatile在多線程當中經常使用,因為在某一線程多次調用某一個變量,編譯器會進行優化,將該變量存放在在寄存器當中,不會每次都從內存當中讀入。果然該變量同時在其他線程當中被修改,這樣就會發生臟讀取錯誤。
而加上volatile修飾,則會提醒編譯器,這個變量可能會被改變,不能存放到寄存器當中,需要每次都從內存當中讀取。
最后,我們說一下join()和detach()的使用技巧。
