就在幾年前,用多線(xiàn)程執行程序還是一件非比尋常的事。然而今天互聯(lián)網(wǎng)應用服務(wù)程序普遍使用多線(xiàn)程來(lái)提高與多客戶(hù)鏈接時(shí)的效率;為了達到最大的吞吐量,事務(wù)服務(wù)器在單獨的線(xiàn)程上運行服務(wù)程序;GUI應用程序將那些費時(shí),復雜的處理以線(xiàn)程的形式單獨運行,以此來(lái)保證用戶(hù)界面能夠及時(shí)響應用戶(hù)的操作。這樣使用多線(xiàn)程的例子還有很多。
但是C++標準并沒(méi)有涉及到多線(xiàn)程,這讓程序員們開(kāi)始懷疑是否可能寫(xiě)出多線(xiàn)程的C++程序。盡管不可能寫(xiě)出符合標準的多線(xiàn)程程序,但是程序員們還是會(huì )使用支持多線(xiàn)程的操作系統提供的多線(xiàn)程庫來(lái)寫(xiě)出多線(xiàn)程C++程序。但是這樣做至少有兩個(gè)問(wèn)題:這些庫大部分都是用C語(yǔ)言完成的,如果在C++程序中要使用這些庫就必須十分小心;還有,每一個(gè)操作系統都有自己的一套支持多線(xiàn)程的類(lèi)庫。因此,這樣寫(xiě)出來(lái)得代碼是沒(méi)有標準可循的,也不是到處都適用的(non-portable)。Boost線(xiàn)程庫就是為了解決所有這些問(wèn)題而設計的。
Boost是由C++標準委員會(huì )類(lèi)庫工作組成員發(fā)起,致力于為C++開(kāi)發(fā)新的類(lèi)庫的組織?,F在它已經(jīng)有近2000名成員。許多庫都可以在Boost源碼的發(fā)布版本中找到。為了使這些類(lèi)庫是線(xiàn)程安全的(thread-safe),Boost線(xiàn)程庫被創(chuàng )建了。
許多C++專(zhuān)家都投身于Boost線(xiàn)程庫的開(kāi)發(fā)中。所有接口的設計都是從0開(kāi)始的,并不是C線(xiàn)程API的簡(jiǎn)單封裝。許多C++特性(比如構造函數和析構函數,函數對象(functionobject)和模板)都被使用在其中以使接口更加靈活?,F在的版本可以在POSIX,Win32和Macintosh Carbon平臺下工作。
起先,大家認為傳統C創(chuàng )建線(xiàn)程的方法似乎比這樣的設計更有用,因為C創(chuàng )建線(xiàn)程的時(shí)候會(huì )傳入一個(gè)void*指針,通過(guò)這種方法就可以傳入數據。然而,由于Boost線(xiàn)程庫是使用函數對象來(lái)代替函數指針,那么函數對象本身就可以攜帶線(xiàn)程所需的數據。這種方法更具靈活性,也是類(lèi)型安全(type-safe)的。當和Boost.Bind這樣的功能庫一起使用時(shí),這樣的方法就可以讓你傳遞任意數量的數據給新建的線(xiàn)程。
目前,由Boost線(xiàn)程庫創(chuàng )建的線(xiàn)程對象功能還不是很強大。事實(shí)上它只能做兩項操作。線(xiàn)程對象可以方便使用==和!=進(jìn)行比較來(lái)確定它們是否是代表同一個(gè)線(xiàn)程;你還可以調用boost::thread::join來(lái)等待線(xiàn)程執行完畢。其他一些線(xiàn)程庫可以讓你對線(xiàn)程做一些其他操作(比如設置優(yōu)先級,甚至是取消線(xiàn)程)。然而,由于要在普遍適用(portable)的接口中加入這些操作不是簡(jiǎn)單的事,目前仍在討論如何將這些操組加入到Boost線(xiàn)程庫中。
Listing1展示了boost::thread類(lèi)的一個(gè)最簡(jiǎn)單的用法。 新建的線(xiàn)程只是簡(jiǎn)單的在std::out上打印“hello,world”,main函數在它執行完畢之后結束。
例1:
#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();return 0;}互斥體的概念有不少變種。Boost線(xiàn)程庫支持兩大類(lèi)互斥體,包括簡(jiǎn)單互斥體(simplemutex)和遞歸互斥體(recursivemutex)。如果同一個(gè)線(xiàn)程對互斥體上了兩次鎖,就會(huì )發(fā)生死鎖(deadlock),也就是說(shuō)所有的等待解鎖的線(xiàn)程將一直等下去。有了遞歸互斥體,單個(gè)線(xiàn)程就可以對互斥體多次上鎖,當然也必須解鎖同樣次數來(lái)保證其他線(xiàn)程可以對這個(gè)互斥體上鎖。
在這兩大類(lèi)互斥體中,對于線(xiàn)程如何上鎖還有多個(gè)變種。一個(gè)線(xiàn)程可以有三種方法來(lái)對一個(gè)互斥體加鎖:
似乎最佳的互斥體類(lèi)型是遞歸互斥體,它可以使用所有三種上鎖形式。然而每一個(gè)變種都是有代價(jià)的。所以Boost線(xiàn)程庫允許你根據不同的需要使用最有效率的互斥體類(lèi)型。Boost線(xiàn)程庫提供了6中互斥體類(lèi)型,下面是按照效率進(jìn)行排序:
boost::mutex,boost::try_mutex,boost::timed_mutex,boost::recursive_mutex,boost::recursive_try_mutex,boost::recursive_timed_mutex
這種方法保證正確的使用互斥體。然而,有一點(diǎn)必須注意:盡管ScopeLock模式可以保證互斥體被解鎖,但是它并沒(méi)有保證在異常拋出之后貢獻資源仍是可用的。所以就像執行單線(xiàn)程程序一樣,必須保證異常不會(huì )導致程序狀態(tài)異常。另外,這個(gè)已經(jīng)上鎖的對象不能傳遞給另一個(gè)線(xiàn)程,因為它們維護的狀態(tài)并沒(méi)有禁止這樣做。
List2給出了一個(gè)使用boost::mutex的最簡(jiǎn)單的例子。例子中共創(chuàng )建了兩個(gè)新的線(xiàn)程,每個(gè)線(xiàn)程都有10次循環(huán),在std::cout上打印出線(xiàn)程id和當前循環(huán)的次數,而main函數等待這兩個(gè)線(xiàn)程執行完才結束。std::cout就是共享資源,所以每一個(gè)線(xiàn)程都使用一個(gè)全局互斥體來(lái)保證同時(shí)只有一個(gè)線(xiàn)程能向它寫(xiě)入。
許多讀者可能已經(jīng)注意到List2中傳遞數據給線(xiàn)程還必須的手工寫(xiě)一個(gè)函數。盡管這個(gè)例子很簡(jiǎn)單,如果每一次都要寫(xiě)這樣的代碼實(shí)在是讓人厭煩的事。別急,有一種簡(jiǎn)單的解決辦法。函數庫允許你通過(guò)將另一個(gè)函數綁定,并傳入調用時(shí)需要的數據來(lái)創(chuàng )建一個(gè)新的函數。List3向你展示了如何使用Boost.Bind庫來(lái)簡(jiǎn)化List2中的代碼,這樣就不必手工寫(xiě)這些函數對象了。
例2:
#include <boost/thread/thread.hpp>#include <boost/thread/mutex.hpp>#include <iostream>boost::mutex io_mutex;struct count{count(int id) : id(id) { }void operator()(){for (int i = 0; i < 10; ++i){boost::mutex::scoped_locklock(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;}例3:// 這個(gè)例子和例2一樣,除了使用Boost.Bind來(lái)簡(jiǎn)化創(chuàng )建線(xiàn)程攜帶數據,避免使用函數對象
#include <boost/thread/thread.hpp>#include <boost/thread/mutex.hpp>#include <boost/bind.hpp>#include <iostream>boost::mutex io_mutex;void count(int id){for (int i = 0; i < 10; ++i){boost::mutex::scoped_locklock(io_mutex);std::cout << id << ": " <<i << std::endl;}}int main(int argc, char* argv[]){boost::thread thrd1(boost::bind(&count, 1));boost::thread thrd2(boost::bind(&count, 2));thrd1.join();thrd2.join();return 0;}條件變量的使用總是和互斥體及共享資源聯(lián)系在一起的。線(xiàn)程首先鎖住互斥體,然后檢驗共享資源的狀態(tài)是否處于可使用的狀態(tài)。如果不是,那么線(xiàn)程就要等待條件變量。要指向這樣的操作就必須在等待的時(shí)候將互斥體解鎖,以便其他線(xiàn)程可以訪(fǎng)問(wèn)共享資源并改變其狀態(tài)。它還得保證從等到得線(xiàn)程返回時(shí)互斥體是被上鎖得。當另一個(gè)線(xiàn)程改變了共享資源的狀態(tài)時(shí),它就要通知正在等待條件變量得線(xiàn)程,并將之返回等待的線(xiàn)程。
List4是一個(gè)使用了boost::condition的簡(jiǎn)單例子。有一個(gè)實(shí)現了有界緩存區的類(lèi)和一個(gè)固定大小的先進(jìn)先出的容器。由于使用了互斥體boost::mutex,這個(gè)緩存區是線(xiàn)程安全的。put和get使用條件變量來(lái)保證線(xiàn)程等待完成操作所必須的狀態(tài)。有兩個(gè)線(xiàn)程被創(chuàng )建,一個(gè)在buffer中放入100個(gè)整數,另一個(gè)將它們從buffer中取出。這個(gè)有界的緩存一次只能存放10個(gè)整數,所以這兩個(gè)線(xiàn)程必須周期性的等待另一個(gè)線(xiàn)程。為了驗證這一點(diǎn),put和get在std::cout中輸出診斷語(yǔ)句。最后,當兩個(gè)線(xiàn)程結束后,main函數也就執行完畢了。
#include <boost/thread/thread.hpp>#include <boost/thread/mutex.hpp>#include <boost/thread/condition.hpp>#include <iostream>const int BUF_SIZE = 10;const int ITERS = 100;boost::mutex io_mutex;class buffer{public:typedef boost::mutex::scoped_lockscoped_lock;buffer(): p(0), c(0), full(0){}void put(int m){scoped_lock lock(mutex);if (full == BUF_SIZE){{boost::mutex::scoped_locklock(io_mutex);std::cout <<"Buffer is full. Waiting..."<< std::endl;}while (full == BUF_SIZE)cond.wait(lock);}buf[p] = m;p = (p+1) % BUF_SIZE;++full;cond.notify_one();}int get(){scoped_lock lk(mutex);if (full == 0){{boost::mutex::scoped_locklock(io_mutex);std::cout <<"Buffer is empty. Waiting..."<< std::endl;}while (full == 0)cond.wait(lk);}int i = buf[c];c = (c+1) % BUF_SIZE;--full;cond.notify_one();return i;}private:boost::mutex mutex;boost::condition cond;unsigned int p, c, full;int buf[BUF_SIZE];};buffer buf;void writer(){for (int n = 0; n < ITERS; ++n){{boost::mutex::scoped_locklock(io_mutex);std::cout << "sending: "<< n << std::endl;}buf.put(n);}}void reader(){for (int x = 0; x < ITERS; ++x){int n = buf.get();{boost::mutex::scoped_locklock(io_mutex);std::cout << "received: "<< n << std::endl;}}}int main(int argc, char* argv[]){boost::thread thrd1(&reader);boost::thread thrd2(&writer);thrd1.join();thrd2.join();return 0;}有兩種方法可以讓不可重用的函數變成可重用的函數。第一種方法就是改變接口,用指針或引用代替原先使用靜態(tài)數據的地方。比方說(shuō),POSIX定義了strok_r,std::strtok中的一個(gè)可重入的變量,它用一個(gè)額外的char**參數來(lái)代替靜態(tài)數據。這種方法很簡(jiǎn)單,而且提供了可能的最佳效果。但是這樣必須改變公共接口,也就意味著(zhù)必須改代碼。另一種方法不用改變公有接口,而是用本地存儲線(xiàn)程(thread localstorage)來(lái)代替靜態(tài)數據(有時(shí)也被成為特殊線(xiàn)程存儲,thread-specific storage)。
Boost線(xiàn)程庫提供了智能指針boost::thread_specific_ptr來(lái)訪(fǎng)問(wèn)本地存儲線(xiàn)程。每一個(gè)線(xiàn)程第一次使用這個(gè)智能指針的實(shí)例時(shí),它的初值是NULL,所以必須要先檢查這個(gè)它的只是否為空,并且為它賦值。Boost線(xiàn)程庫保證本地存儲線(xiàn)程中保存的數據會(huì )在線(xiàn)程結束后被清除。
List5是一個(gè)使用boost::thread_specific_ptr的簡(jiǎn)單例子。其中創(chuàng )建了兩個(gè)線(xiàn)程來(lái)初始化本地存儲線(xiàn)程,并有10次循環(huán),每一次都會(huì )增加智能指針指向的值,并將其輸出到std::cout上(由于std::cout是一個(gè)共享資源,所以通過(guò)互斥體進(jìn)行同步)。main線(xiàn)程等待這兩個(gè)線(xiàn)程結束后就退出。從這個(gè)例子輸出可以明白的看出每個(gè)線(xiàn)程都處理屬于自己的數據實(shí)例,盡管它們都是使用同一個(gè)boost::thread_specific_ptr。
例5:
#include <boost/thread/thread.hpp>#include <boost/thread/mutex.hpp>#include <boost/thread/tss.hpp>#include <iostream>boost::mutex io_mutex;boost::thread_specific_ptr<int> ptr;struct count{count(int id) : id(id) { }void operator()(){if (ptr.get() == 0)ptr.reset(new int(0));for (int i = 0; i < 10; ++i){(*ptr)++;boost::mutex::scoped_locklock(io_mutex);std::cout << id << ": "<< *ptr << 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;}解決這個(gè)問(wèn)題的方法就是所謂的“一次實(shí)現”(onceroutine)。“一次實(shí)現”在一個(gè)應用程序只能執行一次。如果多個(gè)線(xiàn)程想同時(shí)執行這個(gè)操作,那么真正執行的只有一個(gè),而其他線(xiàn)程必須等這個(gè)操作結束。為了保證它只被執行一次,這個(gè)routine由另一個(gè)函數間接的調用,而這個(gè)函數傳給它一個(gè)指針以及一個(gè)標志著(zhù)這個(gè)routine是否已經(jīng)被調用的特殊標志。這個(gè)標志是以靜態(tài)的方式初始化的,這也就保證了它在編譯期間就被初始化而不是運行時(shí)。因此也就沒(méi)有多個(gè)線(xiàn)程同時(shí)將它初始化的問(wèn)題了。Boost線(xiàn)程庫提供了boost::call_once來(lái)支持“一次實(shí)現”,并且定義了一個(gè)標志boost::once_flag及一個(gè)初始化這個(gè)標志的宏BOOST_ONCE_INIT。
List6是一個(gè)使用了boost::call_once的例子。其中定義了一個(gè)靜態(tài)的全局整數,初始值為0;還有一個(gè)由BOOST_ONCE_INIT初始化的靜態(tài)boost::once_flag實(shí)例。main函數創(chuàng )建了兩個(gè)線(xiàn)程,它們都想通過(guò)傳入一個(gè)函數調用boost::call_once來(lái)初始化這個(gè)全局的整數,這個(gè)函數是將它加1。main函數等待著(zhù)兩個(gè)線(xiàn)程結束,并將最后的結果輸出的到std::cout。由最后的結果可以看出這個(gè)操作確實(shí)只被執行了一次,因為它的值是1。
#include <boost/thread/thread.hpp>#include <boost/thread/once.hpp>#include <iostream>int i = 0;boost::once_flag flag =BOOST_ONCE_INIT;void init(){++i;}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 << i << std::endl;return 0;}Boost線(xiàn)程庫已經(jīng)作為標準中的類(lèi)庫技術(shù)報告中的附件提交給C++標準委員會(huì ),它的出現也為下一版C++標準吹響了第一聲號角。委員會(huì )成員對Boost線(xiàn)程庫的初稿給予了很高的評價(jià),當然他們還會(huì )考慮其他的多線(xiàn)程庫。他們對在C++標準中加入對多線(xiàn)程的支持非常感興趣。從這一點(diǎn)上也可以看出,多線(xiàn)程在C++中的前途一片光明。
更多查看:
聯(lián)系客服