利用C++對象的生命週期讓你的代碼看起來更簡潔高效

在native用c++ coding的時候,多線程併發是很常見的情況,這個時候需要用鎖來管理公共資源;還有一種常見情況是,在debug的時候需要關注一個函數進來和出去的信息。
這兩種情況會增加一些額外的代碼,可能使你的代碼看起來不是那麼美好,而且在函數比較大且出口比較多的時候還容易忘記在某個出口進行操作,如果只是打印信息只是統計不出來,如果是忘記了釋放鎖,那這個線程就死鎖了會造成更多穩定性問題。

通過利用C++對象的生命週期可以讓你的代碼變的更簡潔美好而且高效,android系統源代碼裏大量的使用了類似的設計。

1.多線程併發是很常見的情況,這個時候需要用鎖來管理公共資源

假如一個函數大概是這樣的,我們要通過鎖來鎖住裏邊的資源,每一個出口都要加上pthread_mutex_unclok,是不是看上去不爽,而且還容易忘。

int func1() {
pthread_mutex_lock();
if(con1) {
    Res.value=1;
    pthread_mutex_unlock();
    return Res;

}
Res.op1();
Res.op2();
if(con2){
    Res.op3();
    pthread_mutex_unlock();
    return Res;
}
.
.
.
Res.op18()
return Res;
pthread_mutex_unlock();
}

android源碼裏提供了一個Mutex完美的解決了這個問題,使用Mutex後函數變成了這樣的。
是不是簡潔了好多,還不用擔心忘記在哪個出口釋放。

int func1() {
Mutex::Autolock lock(mLock);
if(con1) {
    Res.value=1;
    return Res;

}
Res.op1();
Res.op2();
if(con2){
    Res.op3();
    return Res;
}
.
.
.
Res.op18()
return Res;
}

其實原理很簡單,利用了lock這個對象的生命週期,和它的構造函數和析構函數。func1函數進來的時候創建了Mutex::Autolock類的臨時變量對象lock,會執行構造函數在構造函數裏lock住mLock,當func1函數返回退出的時候無論在哪個出口,函數調用棧結束的最後,臨時變量對象lock的生命週期結束,並對對象進行回收執行析構函數,在析構函數裏unlock mLock,完成整個鎖和解鎖的過程。
不過在早期的ndk裏並沒有提供這個api,後來的版本里有了,所以爲了程序的兼容性,可以自己實現 Mutex和Autolock,就是拷貝android的源代碼了,100行都不到。下邊是我在normandie項目裏拷貝的源代碼,熟悉c++語法的話很容易理解。

/*
* AutoMutex from AOSP
*/
/#ifndef _NMD_UTILS_AUTOMUTEX_H
/#define _NMD_UTILS_AUTOMUTEX_H
/#include <stdint.h>
/#include <sys/types.h>
/#include <time.h>
/#include <pthread.h>
/#include <android/Errors.h>
/#include <android/Timers.h>

/* Simple mutex class.  The implementation is system-dependent.
* The mutex must be unlocked by the thread that locked it.  They are not
* recursive, i.e. the same thread can't lock it multiple times.
*/
class NmdMutex {
public:
    enum {
        PRIVATE = 0,
        SHARED = 1
    };

    NmdMutex();
    NmdMutex(const char* name);
    NmdMutex(int type, const char* name = NULL);
    ~NmdMutex();

    // lock or unlock the mutex
    status_t    lock();
    void        unlock();

    // lock if possible; returns 0 on success, error otherwise
    status_t    tryLock();

    // lock the mutex, but don't wait longer than timeoutMilliseconds.
    // Returns 0 on success, TIMED_OUT for failure due to timeout expiration.
    //
    // OSX doesn't have pthread_mutex_timedlock() or equivalent. To keep
    // capabilities consistent across host OSes, this method is only available
    // when building Android binaries.
    status_t    timedLock(nsecs_t timeoutMilliseconds);

    // Manages the mutex automatically. It'll be locked when Autolock is
    // constructed and released when Autolock goes out of scope.
    class Autolock {
    public:
        inline Autolock(NmdMutex& mutex) : mLock(mutex)  { mLock.lock(); }
        inline Autolock(NmdMutex* mutex) : mLock(*mutex) { mLock.lock(); }
        inline ~Autolock() { mLock.unlock(); }
    private:
        NmdMutex& mLock;
    };

private:
    // A mutex cannot be copied
    NmdMutex(const NmdMutex&);
    NmdMutex&      operator = (const NmdMutex&);
    pthread_mutex_t mMutex;
};

// ---------------------------------------------------------------------------

inline NmdMutex::NmdMutex() {
    pthread_mutex_init(&mMutex, NULL);
}

inline NmdMutex::NmdMutex(__attribute__((unused)) const char* name) {
    pthread_mutex_init(&mMutex, NULL);
}

inline NmdMutex::NmdMutex(int type, __attribute__((unused)) const char* name) {
    if (type == SHARED) {
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
        pthread_mutex_init(&mMutex, &attr);
        pthread_mutexattr_destroy(&attr);
    } else {
        pthread_mutex_init(&mMutex, NULL);
    }
}

inline NmdMutex::~NmdMutex() {
    pthread_mutex_destroy(&mMutex);
}

inline status_t NmdMutex::lock() {
    return -pthread_mutex_lock(&mMutex);
}

inline void NmdMutex::unlock() {
    pthread_mutex_unlock(&mMutex);
}

inline status_t NmdMutex::tryLock() {
    return -pthread_mutex_trylock(&mMutex);
}

inline status_t NmdMutex::timedLock(nsecs_t timeoutNs) {
    const struct timespec ts = {
        /* .tv_sec = */ static_cast<time_t>(timeoutNs / 1000000000),
        /* .tv_nsec = */ static_cast<long>(timeoutNs % 1000000000),
    };
    return -pthread_mutex_timedlock(&mMutex, &ts);
}

// ---------------------------------------------------------------------------

/*
 * Automatic mutex.  Declare one of these at the top of a function.
 * When the function returns, it will go out of scope, and release the
* mutex.
*/
typedef NmdMutex::Autolock NmdAutoMutex;

/#endif // _NMD_UTILS_AUTOMUTEX_H

2.還有一種常見情況是,在debug的時候需要關注一個函數進來和出去的信息

比如我想統計一下某個函數的耗時,或者想觀察它是不是正常退出了。一般都會這麼寫。是不是看着有點上火。

void func2() {
log(“in”);
do1();
if(con1) {
    do2();
    log(“out”);
    return;
}
if(con2) {
    do3();
    log(“out”);
    return;
}
if(con3) {
    do4();
    log(“out”);
    return;
}
if(con4) {
    do5();
    log(“out”);
    return;
}
log(“out”);

}

還可以這麼寫

void func2() {
TimeCacl cacl(“func2”);
do1();
if(con1) {
    do2();
    return;
}
if(con2) {
    do3();
    return;
}
if(con3) {
    do4();
    return;
}
if(con4) {
    do5();
    return;
}

}

只需要定義一個類

class TimeCacl {
public:
    TimeCacl(char* func_name) {
        mFuncName.append(func_name);
        log(“%s in”, mFuncName.c_str());
    }
    ~TimeCacl() {
        log(“%s in”, mFuncName.c_str());
    }
private:
    String8 mFuncName;
}

跟Mutex::Autolock異曲同工,都是利用了對象的生命週期來達成目的的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章