[Boost] ASIO學習筆記:thread

posted in: ASIO, boost, C/C++程式設計, 遊戲 | 1

在前面的例子中,我們都是以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(); 
} 
dorgon

dorgon

職業:LV3遊戲軟體工程師 為了追尋小時候玩遊戲的感動,而一頭栽入遊戲業界。 本來以撰寫遊戲劇本為主要志向,但回過神來才發現已經踏入程序猿的不歸路。 專長為client端跨平台遊戲開發架構與自動建置流程,主要使用引擎為cocos2d-x與UnrealEngine4。

More Posts - Website

Follow Me:
FacebookLinkedIn

One Response

  1. Kelly
    | 回覆

    牛人!

有什麼想法嗎?請發表你的看法