boost多線程入門介紹

 

本文的主要參考資料爲 BOOST線程徹底攻略 - 基礎篇 這裏主要是對其中的例程進行學習,相關說明還請參考原文。css

1 建立一個簡單的多線程示例

在boost中建立一個 boost::thread 類的對象就表明一個可執行的線程。該類的定義在boost/thread/thread.hpp中,最簡單的使用方式是直接傳遞給其一個函數對象或函數指針,建立thread後會自動新建一個線程並馬上執行函數對象或指針的()操做。boost::thread類提供了join方法來等待線程對象的結束。
下面這個示例演示了線程的基本建立方法,而且主程序在等待線程結束以後再退出,爲了方便觀察這裏還加上一個額外的等待語句。 html

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

2 臨界資源的互斥訪問

所謂的臨界資源就是那些每次只容許一個線程訪問的資源,硬件的臨界資源有打印機等,軟件的臨界資源有緩衝區等。標準輸出流中的緩衝區就是一個臨界資源,對應的線程中訪問臨界資源的代碼咱們就稱之爲臨界區,如cout<<"Hello!"<<endl;這段代碼就是臨界區,它向標準輸出流的緩衝區中寫入一段字符並用endl來刷新緩衝區。很顯然若是有多個線程都同時向標準輸出設備的緩衝區中寫數據,那麼打印出來的結果有多是無效的。這時候就須要互斥鎖來保證臨界資源的互斥訪問了。通常在訪問臨界區以前都要上鎖,退出臨界區後再解鎖。
互斥鎖根據所需的功能不一樣,執行效率有所區別。boost::mutex就是一種簡單高效的互斥鎖類型。爲了保證互斥鎖老是可以解鎖,boost將加鎖和解鎖的動做分別封裝到一個類的構造函數和析構函數中,因爲類再離開其做用域或拋出異常後老是能執行其析構函數,全部不會出現死鎖的事情,這個封裝的類就是boost::mutex::scoped_lock。互斥鎖類定義在boost/thread/mutex.hpp中。下例就是一個對cout互斥訪問的例子: java

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
 
class MyCout
{
private:
    int Id;
    static boost::mutex IoMutex;
public:
    MyCout(int id):Id(id){}
    void operator()()
    {
        for(int i = 0;< 5;++i)
        {
            boost::mutex::scoped_lock lock(IoMutex);
 
            std::cout<<"線程"<<Id<<""<<i<<std::endl;
        }
    }
};
 
boost::mutex MyCout::IoMutex;
 
int main(int argc,char* argv[])
{
    boost::thread thrd1(MyCout(1));
    boost::thread thrd2(MyCout(2));
    thrd1.join();
    thrd2.join();
    int i;
    std::cin>>i;
    return 0;
}

上例定義的函數對象MyCout中有兩個成員變量,一個是用來表示線程的ID,另外一個爲靜態成員的互斥鎖用來保證全部經過該函數類所產生的函數對象都會互斥的使用臨界資源。注意靜態成員必需要在類的外部進行定義,在類內部只是進行了聲明。python

3 使用boost::bind給函數綁定數據

前面使用函數對象來執行線程,使得其不但能保存程序並且還能保存相關的數據。咱們使用boost::bind將函數和數據綁定到一塊兒也能實現相同的功能,該類的定義位於boost/bind.hpp之中。ios

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>
 
boost::mutex IoMutex;
 
void MyCout(int id)
{
    for(int i = 0;< 5;++i)
    {
        boost::mutex::scoped_lock lock(IoMutex);
        std::cout<<"線程"<<id<<""<<i<<std::endl;
    }
}
 
int main(int argc,char* argv[])
{
    boost::thread thrd1(boost::bind(&MyCout,1));
    boost::thread thrd2(boost::bind(&MyCout,2));
    thrd1.join();
    thrd2.join();
    int i;
    std::cin>>i;
    return 0;
}

4 條件變量

有時候僅僅是保證臨界資源的互斥方式是不能知足咱們的要求的,好比對於一個緩衝區操做的線程,除了保證緩衝區的互斥訪問以外,對於寫入的線程還必需要保證緩衝區不滿,對於從緩衝區中讀取的線程還必需要保證緩衝區非空。固然咱們可使用一個變量來判斷緩衝區是否可操做,對於讀取緩衝區咱們可用判斷DataNum != 0來進行操做。可是這樣作會增長程序的判斷邏輯使程序結構變得複雜。boost提供了這種等待條件變量的機制。在代碼(GetData線程)進入臨界區前,首先鎖住互斥變量,而後檢驗臨界資源是否可用,若是不可用(緩衝區爲空),那麼線程就會等待(調用wait方法),等待的過程當中會解鎖互斥量以便其餘線程可以運行以改變臨界資源的狀態,同時也會阻塞當前線程防止當前線程和其餘線程同時訪問臨界區。若是某個線程(PutData線程)改變了臨界資源使其對該線程來講可用了,那麼在那個線程(PutData線程)運行完畢後應該調用notify_one來取消wait方法阻塞的一個線程,在從wait方法返回以前,wait會再次給互斥量加鎖。
下面這個示例是一個使用條件變量的例子,其中全局互斥量IoMutex和以前的例子同樣,使用來保證標準輸出流的緩衝區進行互斥訪問。而類中的互斥量就是用來保證類中的方法是互斥的使用的。條件變量有多種這裏咱們使用的是condition,這個類型是condition_variable_any的別名,定義在boost/condition.hpp中。 c++

#include <boost/thread/thread.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>
 
typedef boost::mutex::scoped_lock scoped_lock;
 
boost::mutex IoMutex;
const int BufferSize = 10;
const int Iters = 100;
 
 
class Buffer
{
public:
    Buffer():P(0),C(0),DataNum(0){}
 
    void Write(int m)
    {
        scoped_lock lock(Mutex);
        if(DataNum == BufferSize)
        {
            {
                scoped_lock ioLock(IoMutex);
                std::cout<<"Buffer is full.Waiting..."<<std::endl;
            }
            while(DataNum == BufferSize)
                Cond.wait(lock);
        }
        Buf[P] = m;
        P = (P+1)%BufferSize;    //隊列的入口
        ++DataNum;
        Cond.notify_one();
    }
 
    int Read()
    {
        scoped_lock lock(Mutex);
        if(DataNum == 0)
        {
            {
                scoped_lock ioLock(IoMutex);
                std::cout<<"Buffer is empty.Waiting..."<<std::endl;
            }
            while(DataNum == 0)
                Cond.wait(lock);
        }
        int i = Buf[C];
        C= (C+1)%BufferSize;    //隊列的出口
        --DataNum;
        Cond.notify_one();
        return i;
    }
private:
    int Buf[BufferSize];
    int P;
    int C;
    int DataNum;
    boost::mutex Mutex;
    boost::condition Cond;
};
 
Buffer buf;
 
void Writer()
{
    for(int n = 0;< Iters;++n)
    {
        {
            scoped_lock ioLock(IoMutex);
            std::cout<<"sending:"<<n<<std::endl;
        }
        buf.Write(n);
    }
}
 
void Reader()
{
    for(int i = 0;< Iters;++i)
    {
        int n = buf.Read();
        {
            scoped_lock ioLock(IoMutex);
            std::cout<<"received:"<<n<<std::endl;
        }
    }
}
 
int main(int argc,char* argv[])
{
    boost::thread thrd1(&Writer);
    boost::thread thrd2(&Reader);
    int i;
    std::cin>>i;
    return 0;
}

5 線程局部存儲

通常對於含有靜態變量或者返回指向靜態數據的指針的函數是不可重入的,即同時只能有一個線程可使用該函數,如std::strtok就是不可重入的函數。Boost線程庫提供了智能指針boost::thread_spacific_ptr能夠保證對於不一樣的線程這個指針指向的數據是相互獨立的。因爲不一樣線程指向的數據不一樣,因此每個線程在使用這個指針以前都必需要對其進行賦值。該指針的定義位於boost/thread/tss.hpp中,下面是一個使用示例:git

#include <boost/thread/thread.hpp>
#include <boost/thread/tss.hpp>
#include <boost/thread/mutex.hpp>
#include <iostream>
 
boost::mutex IoMutex;
boost::thread_specific_ptr<int> Ptr;        //指向int的智能指針
 
struct MyCout
{
    MyCout(int id):Id(id){}
 
    void operator()()
    {
        if(Ptr.get() == 0)
            Ptr.reset(new int(0));        //每一個線程使用前都須要給指針賦值
        for(int i = 0;< 5;++i)
        {
            (*Ptr)++;
            boost::mutex::scoped_lock IoLock(IoMutex);
            std::cout<<Id<<""<<*Ptr<<std::endl;
        }
    }
    int Id;
};
 
int main(int argc,char* argv[])
{
    boost::thread thrd1(MyCout(1));
    boost::thread thrd2(MyCout(2));
    int i;
    std::cin>>i;
    return 0;
}
 
# 6 僅運行一次的例程  
 
多線程編程中還存在一個問題,那就是咱們有一個函數可能被不少個線程調用,可是咱們卻只想它執行一次。即若是該函數已經執行過了那麼再次調用時並不會執行。咱們可用經過手動添加全局標誌位的方式來實現。也可使用`boost::call_once`來實現。`call_once`一樣須要藉助於一個全局標誌位`boost::once_flag`而且須要使用宏`BOOST_ONCE_INIT`來初始化這個標誌位。函數和宏定義在`boost/thread/once/hpp`中,下面是一個使用示例:  
 
```c++
#include <boost/thread/thread.hpp>
#include <boost/thread/once.hpp>
#include <iostream>
 
int GlobalVal = 0;        //只想初始化這個變量一次
boost::once_flag Flag= BOOST_ONCE_INIT;
 
void Init()
{
    ++GlobalVal;
}
 
void thread()
{
    boost::call_once(&Init,Flag);
}
 
int main(int argc,char* argv[])
{
    boost::thread thrd1(&thread);
    boost::thread thrd2(&thread);
    thrd1.join();
    thrd2.join();
    std::cout<<GlobalVal<<std::endl;
 
    int i;
    std::cin>>i;
    return 0;
}

上面程序執行完畢後,GlobalVal值爲1,說明確實只執行了一遍初始化函數。github

參考文章:web

關於在類中建立線程的多種方式能夠參考:
boost::thread線程建立方式總結 編程

相關文章
相關標籤/搜索