BOOST 線程徹底攻略

1 建立線程

 首先看看boost::thread的構造函數吧,boost::thread有兩個構造函數: 
(1)thread():構造一個表示當前執行線程的線程對象; 
(2)explicit thread(const boost::function0& threadfunc): 
     boost::function0能夠簡單看爲:一個無返回(返回void),無參數的函數。這裏的函數也能夠是類重載operator()構成的函數;該構造函數傳入的是函數對象而並不是是函數指針,這樣一個具備通常函數特性的類也能做爲參數傳入,在下面有例子。緩存

第一種方式:最簡單方法 安全

void hello() 
{ 
        std::cout << 
        "Hello world, I''m a thread!" 
        << std::endl; 
} 
  
int main(int argc, char* argv[]) 
{ 
        boost::thread thrd(&hello); 
        thrd.join(); 
        return 0; 
}

第二種方式:複雜類型對象做爲參數來建立線程: 多線程

boost::mutex io_mutex; 
struct count 
{ 
        count(int id) : id(id) { } 
        
        void operator()() 
        { 
                for (int i = 0; i < 10; ++i) 
                { 
                        boost::mutex::scoped_lock 
                        lock(io_mutex); 
                        std::cout << id << ": " 
                        << i << std::endl; 
                } 
        } 
        
        int id; 
}; 
  
int main(int argc, char* argv[]) 
{ 
        boost::thread thrd1(count(1)); 
        boost::thread thrd2(count(2)); 
        thrd1.join(); 
        thrd2.join(); 
        return 0; 
}

第三種方式:在類內部建立線程; 函數

class HelloWorld
{
public:
 static void hello()
 {
      std::cout <<
      "Hello world, I''m a thread!"
      << std::endl;
 }
 static void start()
 {
  
  boost::thread thrd( hello );
  thrd.join();
 }
 
}; 
int main(int argc, char* argv[])
{
 HelloWorld::start();
 
 return 0;
} 
在這裏start()和hello()方法都必須是static方法。

(2)若是要求start()和hello()方法不能是靜態方法則採用下面的方法建立線程: this

class HelloWorld
{
public:
 void hello()
 {
    std::cout <<
    "Hello world, I''m a thread!"
    << std::endl;
 }
 void start()
 {
  boost::function0< void> f =  boost::bind(&HelloWorld::hello,this);
  boost::thread thrd( f );
  thrd.join();
 }
 
}; 
int main(int argc, char* argv[])
{
 HelloWorld hello;
 hello.start();
 return 0;
}

第四種方法:用類內部函數在類外部建立線程; spa

class HelloWorld
{
public:
 void hello(const std::string& str)
 {
        std::cout < }
}; 
  
int main(int argc, char* argv[])
{ 
 HelloWorld obj;
 boost::thread thrd( boost::bind(&HelloWorld::hello,&obj,"Hello 
                               world, I''m a thread!" ) ) ;
 thrd.join();
 return 0;
}

  若是線程須要綁定的函數有參數則須要使用boost::bind。好比想使用 boost::thread建立一個線程來執行函數:void f(int i),若是這樣寫:boost::thread thrd(f)是不對的,由於thread構造函數聲明接受的是一個沒有參數且返回類型爲void的型別,並且不提供參數i的值f也沒法運行,這時就能夠寫:boost::thread thrd(boost::bind(f,1))。涉及到有參函數的綁定問題基本上都是boost::thread、boost::function、boost::bind結合起來使用。線程

 

2 互斥體

  任何寫過多線程程序的人都知道避免不一樣線程同時訪問共享區域的重要性。若是一個線程要改變共享區域中某個數據,而與此同時另外一線程正在讀這個數據,那麼結果將是未定義的。爲了不這種狀況的發生就要使用一些特殊的原始類型和操做。其中最基本的就是互斥體(mutex,mutual exclusion的縮寫)。一個互斥體一次只容許一個線程訪問共享區。當一個線程想要訪問共享區時,首先要作的就是鎖住(lock)互斥體。若是其餘的線程已經鎖住了互斥體,那麼就必須先等那個線程將互斥體解鎖,這樣就保證了同一時刻只有一個線程能訪問共享區域。指針

 

  互斥體的概念有很多變種。Boost線程庫支持兩大類互斥體,包括簡單互斥體(simple mutex)和遞歸互斥體(recursive mutex)。若是同一個線程對互斥體上了兩次鎖,就會發生死鎖(deadlock),也就是說全部的等待解鎖的線程將一直等下去。有了遞歸互斥體,單個線程就能夠對互斥體屢次上鎖,固然也必須解鎖一樣次數來保證其餘線程能夠對這個互斥體上鎖。code

 

 

 

在這兩大類互斥體中,對於線程如何上鎖還有多個變種。一個線程能夠有三種方法來對一個互斥體加鎖:對象

 

  1. 一直等到沒有其餘線程對互斥體加鎖。
  2. 若是有其餘互斥體已經對互斥體加鎖就當即返回。
  3. 一直等到沒有其餘線程互斥體加鎖,直到超時。

 

彷佛最佳的互斥體類型是遞歸互斥體,它可使用全部三種上鎖形式。然而每個變種都是有代價的。因此Boost線程庫容許你根據不一樣的須要使用最有效率的互斥體類型。Boost線程庫提供了6中互斥體類型,下面是按照效率進行排序:

 

boost::mutex,

 

boost::try_mutex, 

 

boost::timed_mutex, 

 

boost::recursive_mutex, 

 

boost::recursive_try_mutex,  

 

boost::recursive_timed_mutex 

 

  若是互斥體上鎖以後沒有解鎖就會發生死鎖。這是一個很廣泛的錯誤,Boost線程庫就是要將其變成不可能(至少時很困難)。直接對互斥體上鎖和解鎖對於Boost線程庫的用戶來講是不可能的。mutex類經過teypdef定義在RAII中實現的類型來實現互斥體的上鎖和解鎖。這也就是你們知道的Scope Lock模式。爲了構造這些類型,要傳入一個互斥體的引用。構造函數對互斥體加鎖,析構函數對互斥體解鎖。C++保證了析構函數必定會被調用,因此即便是有異常拋出,互斥體也老是會被正確的解鎖。

3 、條件變量

  有的時候僅僅依靠鎖住共享資源來使用它是不夠的。有時候共享資源只有某些狀態的時候纔可以使用。比方說,某個線程若是要從堆棧中讀取數據,那麼若是棧中沒有數據就必須等待數據被壓棧。這種狀況下的同步使用互斥體是不夠的。另外一種同步的方式--條件變量,就可使用在這種狀況下。

  條件變量的使用老是和互斥體及共享資源聯繫在一塊兒的。線程首先鎖住互斥體,而後檢驗共享資源的狀態是否處於可以使用的狀態。若是不是,那麼線程就要等待條件變量。要指向這樣的操做就必須在等待的時候將互斥體解鎖,以便其餘線程能夠訪問共享資源並改變其狀態。它還得保證從等到得線程返回時互斥體是被上鎖得。當另外一個線程改變了共享資源的狀態時,它就要通知正在等待條件變量得線程,並將之返回等待的線程。

  List4是一個使用了boost::condition的簡單例子。有一個實現了有界緩存區的類和一個固定大小的先進先出的容器。因爲使用了互斥體boost::mutex,這個緩存區是線程安全的。put和get使用條件變量來保證線程等待完成操做所必須的狀態。有兩個線程被建立,一個在buffer中放入100個整數,另外一個將它們從buffer中取出。這個有界的緩存一次只能存放10個整數,因此這兩個線程必須週期性的等待另外一個線程。爲了驗證這一點,put和get在std::cout中輸出診斷語句。最後,當兩個線程結束後,main函數也就執行完畢了。

 

 四、線程局部存儲

  大多數函數都不是可重入的。這也就是說在某一個線程已經調用了一個函數時,若是你再調用同一個函數,那麼這樣是不安全的。一個不可重入的函數經過連續的調用來保存靜態變量或者是返回一個指向靜態數據的指針。 舉例來講,std::strtok就是不可重入的,由於它使用靜態變量來保存要被分割成符號的字符串。

  有兩種方法可讓不可重用的函數變成可重用的函數。第一種方法就是改變接口,用指針或引用代替原先使用靜態數據的地方。比方說,POSIX定義了strok_r,std::strtok中的一個可重入的變量,它用一個額外的char**參數來代替靜態數據。這種方法很簡單,並且提供了可能的最佳效果。可是這樣必須改變公共接口,也就意味着必須改代碼。另外一種方法不用改變公有接口,而是用本地存儲線程(thread local storage)來代替靜態數據(有時也被成爲特殊線程存儲,thread-specific storage)。

  Boost線程庫提供了智能指針boost::thread_specific_ptr來訪問本地存儲線程。每個線程第一次使用這個智能指針的實例時,它的初值是NULL,因此必需要先檢查這個它的只是否爲空,而且爲它賦值。Boost線程庫保證本地存儲線程中保存的數據會在線程結束後被清除。

  List5是一個使用boost::thread_specific_ptr的簡單例子。其中建立了兩個線程來初始化本地存儲線程,並有10次循環,每一次都會增長智能指針指向的值,並將其輸出到std::cout上(因爲std::cout是一個共享資源,因此經過互斥體進行同步)。main線程等待這兩個線程結束後就退出。從這個例子輸出能夠明白的看出每一個線程都處理屬於本身的數據實例,儘管它們都是使用同一個boost::thread_specific_ptr。

相關文章
相關標籤/搜索