Boost線程詳解


一、創建一個線程

創建線程

    boost::thread myThread(threadFun);

    需要注意的是:參數可以是函數對象或者函數指針。並且這個函數無參數並返回void類型

    當一個thread執行完成時,這個子線程就會消失。注意這個線程對象不會消失,它仍然是一個還處在它的生存期的C++對象。同理,當對一個堆上的線程對象的指針調用delete時候,線程對象被銷毀,操作系統的線程並不能保證就消失。

放棄時間片

    boost::thread::yield();

    當前線程放棄余下的時間片。

等待一個線程

    myThread.join();

    調用這個方法的線程進入wait狀態,直到myThread代表的線程完成為止。如果它不結束的話,join方法就不會返回。join是一個等待子線程結束的最好的方法。如果主程序不調用join方法而直接結束,它的子線程有可能沒有執行完成,但是所有的子線程也隨之退出。不調用join方法,主線程就不會等待它的子線程。

 1 #include <iostream>
 2 #include <boost/thread/thread.hpp>
 3 #include <boost/thread/xtime.hpp>
 4  
 5 struct MyThreadFunc {
 6    void operator( )( ) {
 7       // Do something long-running...
 8    }
 9 } threadFun;
10  
11 int main( ) {
12  
13    boost::thread myThread(threadFun); // Create a thread that starts
14                                       // running threadFun
15  
16    boost::thread::yield( );           // Give up the main thread's timeslice
17                                       // so the child thread can get some work
18                                       // done.
19  
20    // Go do some other work...
21  
22    myThread.join( );                  // The current (i.e., main) thread will wait
23                                       // for myThread to finish before it returns
24  
25 }
View Code

線程組

    如果你需要創建幾個線程,考慮使用一個線程組對象thread_group來組織它們。一個thread_group對象可以使用多種方法管理線程。首先,可以使用一個指向動態創建的線程對象的指針作為參數來調用add_thread方法,將這個線程加入線程組。也可以直接使用線程組類的create_thread方法,可不先創建線程而直接把線程加入到線程組中。

    當線程組對象的析構函數被調用時,它將刪除(delete)所有這些通過add_thread方法加入的線程指針。所以,只能將堆上的線程對象指針通過add_thread方法加入線程組。remove_thread方法從線程組刪除某個線程的指針,但是我們仍需負責把線程本身內存釋放掉。

    線程組對象的成員方法join_all方法等待線程組中所有線程結束,才返回。

 1 boost::thread_group grp;
 2 boost::thread *p = new boost::thread(threadFun);
 3 grp.add_thread(p);
 4 //do something...
 5 grp.remove_thread(p);
 6  
 7 grp.create_thread(threadFun);
 8 grp.create_thread(threadFun);   //Now there are two threads in grp
 9  
10 grp.join_all();                 //Wait for all threads to finish
View Code

二、使資源是線程安全的

    保證同一時刻多個線程不會同時修改同一個共享資源,那么這個程序是線程安全的,或者是串行化訪問資源的。可以使用mutex類來控制線程的並發問題。

 1 #include <iostream>
 2 #include <boost/thread/thread.hpp>
 3 #include <string>
 4  
 5 // A simple queue class; don't do this, use std::queue
 6 template<typename T>
 7 class Queue {
 8 public:
 9    Queue( ) {}
10   ~Queue( ) {}
11  
12    void enqueue(const T& x) {
13       // Lock the mutex for this queue
14       boost::mutex::scoped_lock lock(mutex_);
15       list_.push_back(x);
16       // A scoped_lock is automatically destroyed (and thus unlocked)
17       // when it goes out of scope
18    }
19  
20    T dequeue( ) {
21       boost::mutex::scoped_lock lock(mutex_);
22  
23       if (list_.empty( ))
24          throw "empty!";     // This leaves the current scope, so the
25       T tmp = list_.front( ); // lock is released
26       list_.pop_front( );
27       return(tmp);
28    } // Again: when scope ends, mutex_ is unlocked
29  
30 private:
31    std::list<T> list_;
32    boost::mutex mutex_;
33 };
34  
35 Queue<std::string> queueOfStrings;
36  
37 void sendSomething( ) {
38    std::string s;
39    for (int i = 0; i < 10; ++i) {
40       queueOfStrings.enqueue("Cyrus");
41    }
42 }
43  
44 void recvSomething( ) {
45    std::string s;
46  
47    for (int i = 0; i < 10; ++i) {
48       try {s = queueOfStrings.dequeue( );}
49       catch(...) {}
50    }
51 }
52  
53 int main( ) {
54    boost::thread thr1(sendSomething);
55    boost::thread thr2(recvSomething);
56  
57    thr1.join( );
58    thr2.join( );
59 }
View Code

    mutex對象本身並不知道它代表什么,它僅僅是被多個消費者線程使用的資源訪問的鎖定解鎖標志。在某個時刻,只有一個線程可以鎖定這個mutex對象,這就阻止了同一時刻有多個線程並發訪問共享資源。一個mutex就是一個簡單的信號機制。

    給mutex加解鎖有多種策略,最簡單的是使用scoped_lock類,它使用一個mutex參數來構造,並一直鎖定這個mutex直到對象被銷毀。如果這個正在被構造的mutex已經被別的線程鎖定的話,當前線程就會進入wait狀態,直到這個鎖被解開。

三、讀寫鎖

    mutex有一個美中不足,它不區分讀和寫。線程如果只是進行讀操作,mutex強制線程串行化訪問資源,效率低。而且這種操作不需要排他性訪問。基於這個原因,Boost線程庫提供了read_write_mutex

 1 #include <iostream>
 2 #include <boost/thread/thread.hpp>
 3 #include <boost/thread/read_write_mutex.hpp>
 4 #include <string>
 5  
 6 template<typename T>
 7 class Queue {
 8 public:
 9    Queue( ) :  // Use a read/write mutex and give writers priority
10       rwMutex_(boost::read_write_scheduling_policy::writer_priority){}
11   ~Queue( ) {}
12  
13    void enqueue(const T& x) {
14       // Use a r/w lock since enqueue updates the state
15       boost::read_write_mutex::scoped_write_lock writeLock(rwMutex_);
16       list_.push_back(x);
17    }
18  
19    T dequeue( ) {
20       // Again, use a write lock
21       boost::read_write_mutex::scoped_write_lock writeLock(rwMutex_);
22  
23       if (list_.empty( ))
24          throw "empty!";
25       T tmp = list_.front( );
26       list_.pop_front( );
27       return(tmp);
28    }
29  
30    T getFront( ) {
31       // This is a read-only operation, so you only need a read lock
32       boost::read_write_mutex::scoped_read_lock readLock(rwMutex_);
33       if (list_.empty( ))
34          throw "empty!";
35       return(list_.front( ));
36    }
37  
38 private:
39    std::list<T> list_;
40    boost::read_write_mutex rwMutex_;
41 };
42  
43 Queue<std::string> queueOfStrings;
44  
45 void sendSomething( ) {
46    std::string s;
47  
48    for (int i = 0; i < 10; ++i) {
49       queueOfStrings.enqueue("Cyrus");
50    }
51 }
52  
53 void checkTheFront( ) {
54    std::string s;
55  
56    for (int i = 0; i < 10; ++i) {
57       try {s = queueOfStrings.getFront( );}
58       catch(...) {}
59    }
60 }
61  
62 int main( ) {
63  
64    boost::thread thr1(sendSomething);
65    boost::thread_group grp;
66  
67    grp.create_thread(checkTheFront);
68    grp.create_thread(checkTheFront);
69    grp.create_thread(checkTheFront);
70    grp.create_thread(checkTheFront);
71  
72    thr1.join( );
73    grp.join_all( );
74 }
View Code

     注意Queue的構造函數中隊讀寫鎖rwMutex的初始化。同一時刻,可能有多個讀寫線程要鎖定一個read_write_mutex,而這些鎖的調度策略依賴於構造這個mutex時選定的調度策略。Boost庫中提供了四種調度策略:

1)reader_priority:等待讀鎖的線程優先於等待寫鎖的線程

2)writer_priority:等待寫鎖的線程優先於等待讀鎖的線程

3)alternating_single_read:在讀鎖和寫鎖之間交替

4)alternating_many_reads:在讀鎖和寫鎖之間交替,這個策略將在兩個寫鎖之間使得所有的在這個queue上掛起的讀鎖都被允許。

     選擇使用哪種策略要慎重,因為使用前兩種的話可能會導致某些鎖始終不能成功,出現餓死的現象。

死鎖、餓死和競態條件

    1)死鎖,是涉及至少2個線程和2個資源的情況。線程A和B,資源X和Y。A鎖定了X,而B鎖定了Y。此時A和B有彼此想要對方的資源,死鎖就出現了。

  死鎖的預防有兩種方法。一種是,通過小心的按照一定的順序對不同的mutex來加鎖。另一種是,使用Boost提供的try_mutex互斥量和scoped_try_lock。或者使用時間鎖。scoped_try_lock對try_mutex加鎖時,可能成功,也可能失敗,但不會阻塞。時間鎖則有一個超時時間。

 1 bool dequeue(T& x)
 2 {
 3     boost::try_mutex::scope_try_lock lock(tryMutex_);
 4     if(!lock.locked())
 5         return false;
 6     else{
 7         if (list_.empty())
 8             throw "empty!";
 9         x = list_.front();
10         list_.pop_front();
11         return true;
12     }
13 }
14 private:
15     boost::try_mutex tryMutex_;
View Code

  2)餓死,如果你正在使用write_priority策略,並且你有很多創建寫鎖的線程,那么讀鎖的線程就可能餓死。

    3)競態條件,

1 if(q.getFront() == "Cyrus"){
2    str = q.dequeue();
3    //....
4 }

       這個代碼在單線程環境中工作很好,因為q在第一行和第二行代碼之間不會被修改。多線程環境中則會出現問題。此為競態條件。解決的方法是為Queue添加一個成員函數dequeueIfEquals,在函數執行過程中始終鎖定互斥量。

四、從一個線程中給另一個線程發送通知

    當需要線程等待某個事物時,可以創建一個condition對象,然后通過這個對象來通知那些等待的線程。

 1 #include <iostream>
 2 #include <boost/thread/thread.hpp>
 3 #include <boost/thread/condition.hpp>
 4 #include <boost/thread/mutex.hpp>
 5 #include <list>
 6 #include <string>
 7  
 8 class Request { /*...*/ };
 9  
10 // A simple job queue class; don't do this, use std::queue
11 template<typename T>
12 class JobQueue {
13 public:
14    JobQueue( ) {}
15   ~JobQueue( ) {}
16  
17    void submitJob(const T& x) {
18       boost::mutex::scoped_lock lock(mutex_);
19       list_.push_back(x);
20       workToBeDone_.notify_one( );
21    }
22  
23    T getJob( ) {
24       boost::mutex::scoped_lock lock(mutex_);
25  
26       workToBeDone_.wait(lock); // Wait until this condition is
27                                 // satisfied, then lock the mutex
28       T tmp = list_.front( );
29       list_.pop_front( );
30       return(tmp);
31    }
32  
33 private:
34    std::list<T> list_;
35    boost::mutex mutex_;
36    boost::condition workToBeDone_;
37 };
38  
39 JobQueue<Request> myJobQueue;
40  
41 void boss( ) {
42    for (;;) {
43       // Get the request from somewhere
44       Request req;
45       myJobQueue.submitJob(req);
46    }
47 }
48  
49 void worker( ) {
50    for (;;) {
51       Request r(myJobQueue.getJob( ));
52       // Do something with the job...
53    }
54 }
55  
56 int main( ) {
57    boost::thread thr1(boss);
58    boost::thread thr2(worker);
59    boost::thread thr3(worker);
60  
61    thr1.join( );
62    thr2.join( );
63    thr3.join( );
64 }
View Code

boost::mutex::scoped_lock lock(mutex_);

workToBeDone_.wait(lock);

     這兩行代碼,第一行鎖定這個mutex對象。第二行代碼解開這個mutex上的鎖,然后進行等待或者休眠,直到它的條件得到了滿足。這個mutex互斥對象的解鎖讓其他的線程能夠使用這個mutex對象,它們中的某個需要設置這個等待條件,之后通知另外的線程。

    notify_all函數,通知那些所有正在等待某個條件變為真的線程,那些線程隨后進入運行狀態。wait方法做兩件事情:它一直等待直到有人在它正等待的condition上調用notify_one或notify_all,然后它就試圖鎖定相關的mutex。當調用的是notify_all時,盡管多個等待的線程都盡量去獲得下一個鎖,但誰將獲得依賴於這個mutex的類型和使用的優先策略。

    一個condition對象能讓消費者線程休眠,因此在還沒有碰到一個condition時處理器可以去處理別的事情。例如一個web服務器使用一個工作線程池來處理進來的請求。當沒有需求進來時,讓這些子線程處於等待狀態比讓它們循環的查詢或者睡眠然后偶爾喚醒來檢查這個隊列,要好很多。

五、只初始化一次共享資源

 1 #include <iostream>
 2 #include <boost/thread/thread.hpp>
 3 #include <boost/thread/once.hpp>
 4  
 5 // Some sort of connection class that should only be initialized once
 6 struct Conn {
 7    static void init( ) {++i_;}
 8    static boost::once_flag init_;
 9    static int i_;
10    // ...
11 };
12  
13 int Conn::i_ = 0;
14 boost::once_flag Conn::init_ = BOOST_ONCE_INIT;
15  
16 void worker( ) {
17    boost::call_once(Conn::init, Conn::init_);
18    // Do the real work...
19 }
20  
21 Conn c;  // You probably don't want to use a global, so see the
22          // next Recipe
23  
24 int main( ) {
25  
26    boost::thread_group grp;
27  
28    for (int i = 0; i < 100; ++i)
29       grp.create_thread(worker);
30  
31    grp.join_all( );
32  
33    std::cout << c.i_ << '\n'; // c.i_ = 1
34 }
View Code

    一個共享資源不得不在某個地方被初始化,並且你希望第一次使用這個資源的線程來完成初始化工作。一個once_flag類型和call_once函數能夠保證多個線程不會重復的初始化同一個對象。首先,必須使用BOOST_ONCE_INIT宏來初始化這個once_flag對象。boost::once_flag Conn::init_ = BOOST_ONCE_INIT; 之后調用call_once函數,boost::call_once(Conn::init, Conn::init_); 第一個形參是希望被執行一次的初始化函數的地址。

六、給線程函數傳遞一個參數

 1 #include <iostream>
 2 #include <string>
 3 #include <functional>
 4 #include <boost/thread/thread.hpp>
 5  
 6 // A typedef to make the declarations below easier to read
 7 typedef void (*WorkerFunPtr)(const std::string&);
 8  
 9 template<typename FunT,   // The type of the function being called
10          typename ParamT> // The type of its parameter
11 struct Adapter {
12    Adapter(FunT f, ParamT& p) : // Construct this adapter and set the
13       f_(f), p_(&p) {}          // members to the function and its arg
14  
15    void operator( )( ) { // This just calls the function with its arg
16       f_(*p_);        
17    }
18 private:
19    FunT    f_;
20    ParamT* p_;  // Use the parameter's address to avoid extra copying
21 };
22  
23 void worker(const std::string& s) {
24    std::cout << s << '\n';
25 }
26  
27 int main( ) {
28  
29    std::string s1 = "This is the first thread!";
30    std::string s2 = "This is the second thread!";
31  
32    boost::thread thr1(Adapter<WorkerFunPtr, std::string>(worker, s1));
33    boost::thread thr2(Adapter<WorkerFunPtr, std::string>(worker, s2));
34  
35    thr1.join( );
36    thr2.join( );
37 }
View Code

    使用這個函數適配器類模板,你就可以給線程函數傳遞參數了。如果你需要傳遞多個參數,僅需要在這個適配器中增加另一個類型和成員變量。

 

原文鏈接: http://www.cnblogs.com/younes/archive/2010/06/06/1752745.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM