(C⁺⁺) Singleton
in Android Native
Binder를 하나만 열기 위해선
#include <stdio.h>
#include <pthread.h>
#include "StrongPointer.h"
#include "RefBase.h"
#include "Thread.h"
using namespace android;
template <typename TYPE>
class Singleton
{
public:
static TYPE* getInstance() {
Mutex::Autolock _l(sLock); // Mutex 사용
TYPE* instance = sInstance;
if (instance == nullptr) {
instance = new TYPE();
sInstance = instance;
}
return instance;
}
protected:
~Singleton() { }
Singleton() { }
private:
Singleton(const Singleton&);
static Mutex sLock;
static TYPE* sInstance;
};
#define ANDROID_SINGLETON_STATIC_INSTANCE(TYPE) \
template<> Mutex \
(Singleton< TYPE >::sLock)(Mutex::PRIVATE); \
template<> TYPE* Singleton< TYPE >::sInstance(nullptr); /* NOLINT */ \
template class Singleton< TYPE >;
class AAA : public RefBase, public Singleton<AAA>
{
public:
AAA(){printf("AAA::AAA()\n");}
~AAA(){printf("AAA::~AAA()\n");}
};
ANDROID_SINGLETON_STATIC_INSTANCE(AAA);
int main()
{
sp<AAA> p1 = AAA::getInstance(); // Strong pointer
sp<AAA> p2 = AAA::getInstance();
return 0;
}
다짜고짜 코드부터 적었다. 이놈의 안티패턴…기왕 써야한다면 안전하게 써야겠지. 그래서 Android Native에서도 Singleton 구현을 위한 템플릿을 제공한다.
일반적인 구현과 흡사해서 별로 볼건 없지만 공유자원이므로 Mutex를 사용하는 것과 일반적으로 잘 생각하지 않는 Singleton의 객체 소멸까지 고려해서 Strong pointer를 사용한것을 봐보자. 실제 Android에서의 활용예를 살펴보면 먼저 ProcessState의 코드이다.
#include <stdio.h>
#include <pthread.h>
#include "StrongPointer.h"
#include "RefBase.h"
#include "Thread.h"
using namespace android;
class ProcessState;
Mutex& gProcessMutex = *new Mutex;
sp<ProcessState> gProcess;
class ProcessState : public virtual RefBase
{
public:
static sp<ProcessState> self();
private:
explicit ProcessState();
};
ProcessState::ProcessState()
{
printf("open(\"/dev/binder\", O_RDWR | O_CLOEXEC)\n"); // Binder driver를 하나만 열기 위해
}
sp<ProcessState> ProcessState::self()
{
Mutex::Autolock _l(gProcessMutex);
if (gProcess != nullptr) {
return gProcess;
}
gProcess = new ProcessState();
return gProcess;
}
int main()
{
sp<ProcessState>p1 = ProcessState::self();
sp<ProcessState>p2 = ProcessState::self();
return 0;
}
역시나 간략히 추린 코드이며 Android에서는 더 많은 내용을 담고 있다. 말 그대로 Process의 상태를 저장하기 위한 클래스인데 Singleton을 사용해서 구현되어 있다.
이유는 Process 내에서는 Binder driver를 단 하나만 열어야 하기 때문이다. 단순히 의존성을 가져오기 위함이 아닌(전역이나 다름없는) 정말 Singleton 본연의 취지에 맞는 사용법이다 짝짝짝.
Process의 상태를 위한 클래스를 봤으니 이번에는 Thread의 상태를 위한 클래스를 살펴본다. IPCThreadState 클래스이다.
#include <stdio.h>
#include <pthread.h>
#include "StrongPointer.h"
#include "RefBase.h"
#include "Thread.h"
using namespace android;
static pthread_mutex_t gTLSMutex = PTHREAD_MUTEX_INITIALIZER;
static bool gHaveTLS(false);
static pthread_key_t gTLS = 0;
class IPCThreadState
{
public:
static IPCThreadState* self();
static void threadDestructor(void *st)
{
delete (IPCThreadState*)st; // 직접 등록한 해제함수를 호출한다
}
private:
IPCThreadState();
~IPCThreadState();
};
IPCThreadState::IPCThreadState()
{
pthread_setspecific(gTLS, this);
printf("IPCThreadState::IPCThreadState()\n");
}
IPCThreadState::~IPCThreadState()
{
printf("IPCThreadState::~IPCThreadState()\n");
}
IPCThreadState* IPCThreadState::self()
{
if (gHaveTLS) {
restart:
const pthread_key_t k = gTLS; // Thread별 고유공간 여부를 저장하기 위한 운영체제내 영역
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k); // 기존 Singleton을 응용
if (st) return st;
return new IPCThreadState;
}
pthread_mutex_lock(&gTLSMutex);
if (!gHaveTLS) {
int key_create_value = pthread_key_create(&gTLS, threadDestructor);
if (key_create_value != 0) {
pthread_mutex_unlock(&gTLSMutex);
return nullptr;
}
gHaveTLS = true;
}
pthread_mutex_unlock(&gTLSMutex);
goto restart;
}
class AAA : public Thread
{
public :
bool threadLoop()
{
IPCThreadState *t1 = IPCThreadState::self();
IPCThreadState *t2 = IPCThreadState::self();
IPCThreadState *t3 = IPCThreadState::self();
return false;
}
};
class BBB : public Thread
{
public :
bool threadLoop()
{
IPCThreadState *t1 = IPCThreadState::self();
IPCThreadState *t2 = IPCThreadState::self();
return false;
}
};
int main()
{
sp<AAA> t1 = new AAA;
sp<AAA> t2 = new AAA;
t1->run("AAA");
t2->run("BBB");
t1->join();
t2->join();
sleep(3);
return 0;
}
TLS(Thread Local Storage) 개념을 활용하여 Thread별 고유한 저장공간을 구현한 것으로, Android뿐만 아니라 각종 운영체제에서 API 등을 통해서 제공한다. Android에서는 자체적인 공간에 Key를 생성하여 Self()에서 Thread당 한번의 new를 할 수 있도록 구현하였다.