乐趣区

利用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 out”, mFuncName.c_str());
}
private:
String8 mFuncName;
}
跟 Mutex::Autolock 异曲同工,都是利用了对象的生命周期来达成目的的。

退出移动版