使用C++11的function/bind組件封裝Thread以及回調函數的使用


之前在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實現委托機制,更加依賴於回調函數傳入邏輯。


免責聲明!

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



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