在前面的例子中,我們都是以single thread來執行,但在同步/非同步操作中,其實很多問題都需要multi-thread來解決,例如,多人的連線遊戲,就需要多條thread來配給每一個不同的連線。 在進行之前,我們必須要知道的是process跟thread的分別: 當我們在執行一個程式之後,就會產生一個process,這個process擁有以下二種東西: 1.自己的Memory space 2.一個以上的thread 而每一個thread又擁有以下二個東西: 1.Stack:紀錄函數呼叫路徑,以及這些函數所用到的區域變數(local variable) 2.目前CPU的狀態 由於thread有自己的stack,因此每個thread雖然共享同一個Memory space可以存取到彼此之間的物件,但卻無法存取到對方的local variable 由於thread只是多工而不是平行運算,因此OS會依照thread所設定的優先權,分配相應的時間讓其使用CPU。例如:當我們在玩遊戲的時候,遊戲程式執行後會產生一個process,而這個process中會有如播放音樂、更新畫面…等許多的任務,這時候我們可以分配不同的thread去執行並分配其優先順序 創建、分配thread及其優先順序通常是程式設計師自己要寫,當我們使用遊戲引擎如OGRE、hge等來寫遊戲的話,如果沒特別導入如boost::thread這類的函式庫的話,其實都會是single thread的程式,也就是說,我們必須讀完所有的resource才能進行遊戲,不能夠一邊讀resource一邊顯示讀檔的進度。 基本上,當一個程式開始執行的時候,他只會存在一個執行main() function的thread,使用multi-thread的重要性在於,除了他能夠做到以上的事情之外,更重要的是,由於現在的電腦都擁有1顆以上的CPU,因此我們可以更進一步的分配這些thread給不同的CPU來運算 以下展示了一個簡單的boost::thread程式:
void thread(const char* name) { for (int i = 0; i < 5; ++i) { std::cout << name << std::endl; } } int main() { boost::thread t(boost::bind(&thread, "t:")); boost::thread t2(boost::bind(&thread, "t2:")); t.join(); t2.join(); system("pause"); }
執行結果我們可以發現,t跟t2二個程式執行出來的順序其實並不會固定,這是因為thread的調用是靠著OS依目前CPU的狀況來轉移控制權給t或t2。有時候我們可以發現,輸出的結果如果太長的話,常常會在中間就被切一半然後接上別的thread的結果。 其實,當我們執行到boost::thread t(boost::bind(&thread, “t:”)); 時,t就已經開始跟執行main這條thread同步的開始在執行。 join()的目的,則是將這二個thread加入執行main function這個thread的運算流程中,因此main()會被block住,直到所有下join的thread都運算完畢之後才會執行到system(“pause”); 在我們的程式中,實際在運行的thread t跟t2都一定會跟type type boost::thread所宣告的變數綁定在一起,只是,只要當我們thread建立完之後,二者之間的連結關係便不再存在。也就是說,就算是t物件被刪除了,程式也會繼續運行。 我們可以試著做以下的實驗來印證這個想法:
using namespace std; void wait(int seconds) { boost::this_thread::sleep(boost::posix_time::seconds(seconds)); } void thread(const char* name) { for (int i = 0; i < 5; ++i) { wait(1); std::cout << name << i << std::endl; } } int main() { boost::thread* t = new boost::thread(boost::bind(&thread, "t:")); t->join(); delete t; t = NULL; system("pause"); }
t.detach() 可以允許我們將變數與實際變數之間的關連移除,試著執行下面的程式碼並觀看結果:
using namespace std; void wait(int seconds) { boost::this_thread::sleep(boost::posix_time::seconds(seconds)); } void thread(const char* name) { for (int i = 0; i < 5; ++i) { wait(1); std::cout << name << i << std::endl; } } int main() { boost::thread t(boost::bind(&thread, "t:")); t.detach(); t.join(); system("pause"); }
using namespace std; void wait(int seconds) { boost::this_thread::sleep(boost::posix_time::seconds(seconds)); } void thread(const char* name) { for (int i = 0; i < 5; ++i) { wait(1); std::cout << name << i << std::endl; } } int main() { boost::thread t(boost::bind(&thread, "t:")); t.join(); t.detach(); system(pause); }
當t.detach();在t.detach();前面時,我們可以發現join()並不會觸發任何的效果。 boost::thread也提供了用來中斷thread的功能:t->interrupt();
using namespace std; void wait(int seconds) { boost::this_thread::sleep(boost::posix_time::seconds(seconds)); } void thread(const char* name) { for (int i = 0; i < 5; ++i) { try{ wait(1); std::cout << name << i << std::endl; } catch (boost::thread_interrupted& e) { cout << "interrupted:" << endl; return; } } } int main() { boost::thread* t = new boost::thread(boost::bind(&thread, "t:")); wait(2); t->interrupt(); t->join(); system("pause"); }
對於中斷例外的處置我們必須自行在thread中catch。 從上例中可以看到,我們thread對於中斷例外的處置便是結束運算並即刻返回。 另外,boost::thread也提供以下的方法來取得thread的id及硬體上能做到的同步能力。
using namespace std; void wait(int seconds) { boost::this_thread::sleep(boost::posix_time::seconds(seconds)); } void thread(const char* name) { for (int i = 0; i < 5; ++i) { try{ wait(1); std::cout << name << i << std::endl; } catch (boost::thread_interrupted& e) { cout << "interrupted:" << endl; return; } } } int main() { boost::thread t(boost::bind(&thread, "t:")); std::cout << boost::this_thread::get_id() << std::endl; std::cout << t.get_id() << std::endl; std::cout << boost::thread::hardware_concurrency() << std::endl; system("pause"); }
當我們使用雙核心時,呼叫 boost::thread::hardware_concurrency()會回傳2 以下是一個簡單的例子,展示了如何在ASIO中創建多個thread:
using namespace std; boost::asio::io_service io_service; void WorkerThread(){ std::cout < < "Thread Start:" << endl; io_service.run(); std::cout << "Thread Finish:" << endl; } int main( int argc, char * argv[] ){ boost::shared_ptr< boost::asio::io_service::work > work( new boost::asio::io_service::work( io_service )); std::cout < < "Press [Enter] to exit." << std::endl; boost::thread_group worker_threads; for( int x = 0; x < 4; ++x ) { worker_threads.create_thread(WorkerThread); } std::cin.get(); io_service.stop(); worker_threads.join_all(); system("pause"); return 0; }
從程式中我們可以看到boost::shared_ptr< boost::asio::io_service::work > work( new boost::asio::io_service::work( io_service ));這行,目地是用來讓io_service.run()可以持續保持在背景監聽是否有其他非同步的操作加入。然而,這樣子上面的程式豈不是永遠沒辦法結束了嗎?這時導入io_service.stop();這行就可以解決。 io_service.stop()的目的,在於通知io_service停止目前所有的操作,因此,在當目前的工作完成之後,就不會繼續進行操作,在這裡,由於『work object 提供一個work給 io_service object這件事,本身就是一個會被加入到Completion Event Queue裡面的工作』,因此在執行stop()之後,這個工作不會被運行,自然而然就不會有更多的工作被加入Completion Event Queue裡。 另外,需要特別注意的一點觀念是:run() 只會block住他目前所屬於的thread,也就是說,在哪個thread呼叫run(),那麼該thread就會被block住來執行需要同步操作的工作。 worker_threads.join_all();則是將這個群組中的所有thread加入主線程中 另外,如果我們也可以建立多個io_service並分配給不同的thread去運行。如果電腦硬體的CPU數跟我們創建的io_service數目相同時,這些非同步的操作將會用他們自己的CPU來運算,一個程式DEMO如下:
void handler1(const boost::system::error_code &ec) { std::cout < < "5 s." << std::endl; } void handler2(const boost::system::error_code &ec) { std::cout << "5 s." << std::endl; } void run1() { io_service1.run(); } void run2() { io_service2.run(); } int main() { boost::asio::deadline_timer timer1(io_service1, boost::posix_time::seconds(5)); timer1.async_wait(handler1); boost::asio::deadline_timer timer2(io_service2, boost::posix_time::seconds(5)); timer2.async_wait(handler2); boost::thread thread1(run1); boost::thread thread2(run2); thread1.join(); thread2.join(); }
Kelly
牛人!