之前在http://www.cnblogs.com/inevermore/p/4008572.html中采用面向對象的方式,封裝了Posix的線程,那里采用的是虛函數+繼承的方式,用戶通過重寫Thread基類的run方法,傳入自己的用戶邏輯。
現在我們采用C++11的function,將函數作為Thread類的成員,用戶只需要將function對象傳入線程即可,所以Thread的聲明中,應該含有一個function成員變量。
類的聲明如下:
#ifndef THREAD_H_ #define THREAD_H_ #include <boost/noncopyable.hpp> #include <functional> #include <pthread.h> class Thread : boost::noncopyable { public: typedef std::function<void ()> ThreadCallback; Thread(ThreadCallback callback); ~Thread(); void start(); void join(); static void *runInThread(void *); private: pthread_t threadId_; bool isRunning_; ThreadCallback callback_; //回調函數 }; #endif //THREAD_H_
那么如何開啟線程?思路與之前一致,寫一個static函數,用戶pthread_create的第三個參數,this作為最后一個參數即可。
void Thread::start() { pthread_create(&threadId_, NULL, runInThread, this); isRunning_ = true; }
回調函數
注意在這種封裝方式中,我們采用了回調函數。回調函數與普通函數的區別就是,普通函數寫完由我們自己直接調用,函數調用是一種不斷往上堆積的方式,而回調函數通常是我們把某一個函數傳入一個“盒子”,由該盒子內的機制來調用它。
在這個例子里面,我們將function傳入Thread,當Thread啟動的時候,由Thread去執行function對象。
在win32編程中大量用到這種機制,我們為鼠標單擊、雙擊等事件編寫相應的函數,然后將其注冊給windows系統,然后系統在我們觸發各種事件的時候,根據事件的類型,調用相應的構造函數。
關於回調函數,可以參考:http://www.zhihu.com/question/19801131
以后有時間,再專門總結下回調函數。
完整的cpp如下:
#include "Thread.h" Thread::Thread(ThreadCallback callback) : threadId_(0), isRunning_(false), callback_(std::move(callback)) { } Thread::~Thread() { if(isRunning_) { //detach pthread_detach(threadId_); } } void Thread::start() { pthread_create(&threadId_, NULL, runInThread, this); isRunning_ = true; } void Thread::join() { pthread_join(threadId_, NULL); isRunning_ = false; } void *Thread::runInThread(void *arg) { Thread *pt = static_cast<Thread*>(arg); pt->callback_(); //調用回調函數 return NULL; }
這個線程的使用方式有三種:
一是將普通函數作為回調函數
void foo() { while(1) { printf("foo\n"); sleep(1); } } int main(int argc, char const *argv[]) { Thread t(&foo); t.start(); t.join(); return 0; }
二是采用類的成員函數作為回調函數:
class Foo { public: void foo(int i) { while(1) { printf("foo %d\n", i++); sleep(1); } } }; int main(int argc, char const *argv[]) { Foo f; int i = 34; Thread t(bind(&Foo::foo, &f, i)); t.start(); t.join(); return 0; }
最后一種是組合一個新的線程類,注意這里采用的是類的組合:
class Foo { public: Foo() : thread_(bind(&Foo::foo, this)) { } void start() { thread_.start(); thread_.join(); } void foo() { while(1) { printf("foo\n"); sleep(1); } } private: Thread thread_; }; int main(int argc, char const *argv[]) { Foo f; f.start(); return 0; }
有些復雜的類,還需要將三種方式加以整合,例如后面要談到的TimerThread,里面含有一個Thread和Timer,用戶將邏輯注冊給Timer,然后Timer的start函數注冊給Thread。
這種方式的Thread,使用靈活性相對於面向對象的風格,提高了很多。
基於對象和面向對象
這里總結幾點:
面向對象依靠的是虛函數+繼承,用戶通過重寫基類的虛函數,實現自己的邏輯。
基於對象,依賴類的組合,使用function和bind實現委托機制,更加依賴於回調函數傳入邏輯。
