(C⁺⁺) 🧡Thread

in Android Native

5 minute read

#include <stdio.h>  
#include <pthread.h>  
#include "StrongPointer.h"  
#include "RefBase.h"  
  
using namespace android;  
  
void *foo(void* data)  
{  
	printf("foo()\n");  
	return 0;  
}  
  
int main()  
{  
	pthread_t thread;  
	pthread_create( &thread, 0, foo, 0 );  
	pthread_join( thread, 0 );  
	return 0;  
}  

AndroidλŠ” Linux μœ„μ— μ˜¬λΌκ°€ μžˆμœΌλ―€λ‘œ λ¦¬λˆ…μŠ€μ—μ„œμ˜ μ“°λ ˆλ“œλΆ€ν„° μ‚΄νŽ΄λ³΄λ©΄, 기본적인 POSIX threadλŠ” μœ„μ™€ 같이 μ‚¬μš©ν•œλ‹€.

#include <stdio.h>  
#include <pthread.h>  
#include "StrongPointer.h"  
#include "RefBase.h"  
  
using namespace android;  
  
class Thread  
{  
	pthread_t thread;  
public:  
	virtual void foo(void) = 0;  
	static void *_foo(void* data)  
	{  
		Thread *self = static_cast<Thread*>(data);  
		self->foo();  
		return 0;  
	}  
	void run() { pthread_create( &thread, 0, _foo, this ); }  
	void join() { pthread_join( thread, 0 ); }  
};  
  
class MyThread : public Thread  
{  
public:  
	void foo(void)  
	{  
		printf("MyThread::foo()\n");  
	}  
};  
  
int main()  
{  
	MyThread thread;  
	thread.run();  
	thread.join();  
	return 0;  
}  

이것을 클래슀둜 wrappingν•˜λ©΄ μ΄λ ‡κ²Œλ„ μ“Έ 수 μžˆμ„κ²ƒμ΄λ‹€. λ§ˆμ°¬κ°€μ§€λ‘œ Androidμ—μ„œλ„ POSIX threadλ₯Ό wrapν•΄μ„œ κ΅¬ν˜„ν•΄λ†“μ•˜λŠ”λ°,

#ifndef _LIBS_UTILS_THREAD_H  
#define _LIBS_UTILS_THREAD_H  
  
#include <stdint.h>  
#include <sys/types.h>  
#include <time.h>  
  
#if !defined(_WIN32)  
# include <pthread.h>  
#endif  
  
#include "Condition.h"  
#include "Errors.h"  
#include "Mutex.h"  
#include "RefBase.h"  
#include "Timers.h"  
#include "ThreadDefs.h"  
  
// β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”  
namespace android {  
// β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”  
  
// DO NOT USE: please use std::thread  
  
class Thread : virtual public RefBase  
{  
public:  
    // Create a Thread object, but doesn’t create or start the associated  
    // thread. See the run() method.  
    explicit            Thread(bool canCallJava = true);  
    virtual             ~Thread();  
  
    // Start the thread in threadLoop() which needs to be implemented.  
    // NOLINTNEXTLINE(google-default-arguments)  
    virtual status_t    run(    const char* name,  
                                int32_t priority = PRIORITY_DEFAULT,  
                                size_t stack = 0);  
      
    // Ask this object’s thread to exit. This function is asynchronous, when the  
    // function returns the thread might still be running. Of course, this  
    // function can be called from a different thread.  
    virtual void        requestExit();  
  
    // Good place to do one-time initializations  
    virtual status_t    readyToRun();  
      
    // Call requestExit() and wait until this object’s thread exits.  
    // BE VERY CAREFUL of deadlocks. In particular, it would be silly to call  
    // this function from this object’s thread. Will return WOULD_BLOCK in  
    // that case.  
            status_t    requestExitAndWait();  
  
    // Wait until this object’s thread exits. Returns immediately if not yet running.  
    // Do not call from this object’s thread; will return WOULD_BLOCK in that case.  
            status_t    join();  
  
    // Indicates whether this thread is running or not.  
            bool        isRunning() const;  
  
#if defined(__ANDROID__)  
    // Return the thread’s kernel ID, same as the thread itself calling gettid(),  
    // or -1 if the thread is not running.  
            pid_t       getTid() const;  
#endif  
  
protected:  
    // exitPending() returns true if requestExit() has been called.  
            bool        exitPending() const;  
      
private:  
    // Derived class must implement threadLoop(). The thread starts its life  
    // here. There are two ways of using the Thread object:  
    // 1) loop: if threadLoop() returns true, it will be called again if  
    //          requestExit() wasn’t called.  
    // 2) once: if threadLoop() returns false, the thread will exit upon return.  
    virtual bool        threadLoop() = 0;  
  
private:  
    Thread& operator=(const Thread&);  
    static  int             _threadLoop(void* user);  
    const   bool            mCanCallJava;  
    // always hold mLock when reading or writing  
            thread_id_t     mThread;  
    mutable Mutex           mLock;  
            Condition       mThreadExitedCondition;  
            status_t        mStatus;  
    // note that all accesses of mExitPending and mRunning need to hold mLock  
    volatile bool           mExitPending;  
    volatile bool           mRunning;  
            sp<Thread>      mHoldSelf;  
#if defined(__ANDROID__)  
    // legacy for debugging, not used by getTid() as it is set by the child thread  
    // and so is not initialized until the child reaches that point  
            pid_t           mTid;  
#endif  
};  
  
}  // namespace android  
  
// β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”  
#endif // _LIBS_UTILS_THREAD_H  
// β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”β€”  

이것저것 κ΅¬ν˜„μ‚¬ν•­μ€ λ§Žμ§€λ§Œ 기본적인 λ™μž‘μ€ μΈν„°νŽ˜μ΄μŠ€λ§Œ 봐도 μ§μž‘ν•  수 μžˆλ‹€. μ£Όμ˜ν• λ§Œν•œ 사항은 μ“°λ ˆλ“œ 객체λ₯Ό λ‚΄λΆ€μ—μ„œ strong pointer둜 λ§Œλ“  λ’€, refCountλ₯Ό 자체적으둜 1 κ°μ†Œμ‹œν‚¨λ‹€λŠ” 점이닀. κ·Έλž˜μ„œ 일반 ν¬μΈν„°λ‘œ Android μ“°λ ˆλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ refCountκ°€ 0이 λ˜μ–΄λ²„λ¦¬λ―€λ‘œ μ •μƒμ μœΌλ‘œ λŒμ§€μ•ŠλŠ”λ‹€.

즉, λ°˜λ“œμ‹œ spλ₯Ό μ¨μ„œ μ‚¬μš©ν•΄μ•Όν•œλ‹€.
as below

using namespace android;  
  
class MyThread : public Thread  
{  
public:  
	bool threadLoop()  
	{  
		printf("MyThread::foo()\n");  
		sleep(2);  
		return true;  
	}  
};  
  
int main()  
{  
	sp<Thread> thread = new MyThread;  
	thread->run("MyThread");  
	thread->join();  
	return 0;  
}  

λ‹€μŒμœΌλ‘œ μ‚΄νŽ΄λ³Ό λ‚΄μš©μ€ Androidμ—μ„œ μ œκ³΅ν•˜λŠ” Mutex이닀.

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
int sum = 0;  
  
class MyThread : public Thread  
{  
public:  
	bool threadLoop()  
	{  
		int local;  
		for(int i=0; i<10000000; i++)  
		{  
			pthread_mutex_lock(&mutex);  
			local = sum;  
			local = local + 1;  
			sum = local;  
			pthread_mutex_unlock(&mutex);  
		}  
				  
		return false;  
	}  
};  
  
int main()  
{  
	sp<Thread> thread1 = new MyThread;  
	thread1->run("MyThread1");  
	sp<Thread> thread2 = new MyThread;  
	thread2->run("MyThread2");  
  
	thread1->join();  
	thread2->join();  
	printf("sum = %d\n", sum );  
	return 0;  
}  

μ—­μ‹œλ‚˜ POSIX mutexλΆ€ν„° μ‚΄νŽ΄λ³΄λ©΄ μœ„μ™€ 같이 μ‚¬μš©ν•œλ‹€.

using namespace android;  
  
Mutex mutex;  
int sum = 0;  
  
class MyThread : public Thread  
{  
public:  
	bool threadLoop()  
	{  
		int local;  
		for(int i=0; i<10000000; i++)  
		{  
			mutex.lock();  
			local = sum;  
			local = local + 1;  
			sum = local;  
			mutex.unlock();  
		}  
				  
		return false;  
	}  
};  

그리고 더 νŽΈν•˜κ²Œ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ μ—­μ‹œλ‚˜ Androidμ—μ„œ wrapper 클래슀λ₯Ό μ œκ³΅ν•˜λŠ”λ° λ‹€λ§Œ μ—¬κΈ°μ„œ μ£Όμ˜ν•  사항이 μžˆλ‹€. 이 μž„κ³„μ˜μ—­(Critical section)μ—μ„œ μ˜ˆμ™Έλ°œμƒμ‹œ unlock이 μ•ˆλ˜λŠ” λ¬Έμ œκ°€ μžˆμ„ 수 μžˆλ‹€λŠ”κ²ƒμ΄λ‹€. κ·Έλ ‡λ‹€κ³  λͺ¨λ“  catchμ—μ„œ unlock을 ν•˜λŠ” 것도 λ­”κ°€ λΆˆν•©λ¦¬ν•˜λ‹€.

κ·Έλž˜μ„œ Androidμ—μ„œ autolockμ΄λΌλŠ” 것을 μ œκ³΅μ„ ν•œλ‹€. 이것도 μ—­μ‹œ RAII κ°œλ…(μžμ›μ˜ κ΄€λ¦¬λŠ” 객체)이닀.

Mutex mutex;  
int sum = 0;  
  
class MyThread : public Thread  
{  
public:  
	bool threadLoop()  
	{  
		int local;  
		for(int i=0; i<10000000; i++)  
		{  
			Mutex::Autolock ll(mutex);  
			local = sum;  
			local = local + 1;  
			sum = local;  
		}  
				  
		return false;  
	}  
};  

Autolock은 μ˜ˆμ™Έκ°€ λ°œμƒν•˜λ”λΌλ„ μžλ™μœΌλ‘œ unlock을 ν•΄μ€€λ‹€. (call stack에 μ•ˆλ‚¨λ‚˜β€¦?)

μ΄λ²ˆμ—” Conditional에 λŒ€ν•΄ μ•Œμ•„λ³΄μž. 일반적인 μƒμ‚°μž μ†ŒλΉ„μž λͺ¨λΈμ΄ μžˆμ„λ•Œ 각각이 λ³„κ°œμ˜ μ“°λ ˆλ“œμ—μ„œ λ™μž‘ν•œλ‹€λ©΄,
생산도 μ•ˆν–ˆλŠ”λ° μ†ŒλΉ„μžκ°€ μž‘λ™ν•˜λŠ” λ¬Έμ œκ°€ μžˆμ„ 수 μžˆλ‹€. 일의 μˆœμ„œκ°€ μžˆλŠ” 것이닀. 이런 문제λ₯Ό μœ„ν•΄ POSIXλŠ” pthread_cond_wait, pthread_cond_signal λ“±μ˜ apiλ₯Ό μ œκ³΅ν•œλ‹€.

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;  
  
class Producer: public Thread  
{  
public:  
	bool threadLoop()  
	{  
		pthread_mutex_lock(&mutex);  
		for(int i=0; i<3; i++)  
		{  
			sleep(1);  
			printf("생산쀑…\n");  
		}  
		printf("μƒμ‚°μ™„λ£Œ\n");  
		pthread_cond_signal(&cond);  
		pthread_mutex_unlock(&mutex);  
				  
		return false;  
	}  
};  
class Consumer : public Thread  
{  
public:  
	bool threadLoop()  
	{  
		pthread_mutex_lock(&mutex);  
		pthread_cond_wait(&cond, &mutex);  
		for(int i=0; i<3; i++)  
		{  
			sleep(1);  
			printf("μ†ŒλΉ„μ€‘β€¦\n");  
		}  
		printf("μ†ŒλΉ„μ™„λ£Œ\n");  
		pthread_mutex_unlock(&mutex);  
				  
		return false;  
	}  
};  
  
int main()  
{  
	sp<Thread> pro = new Producer;  
	pro->run("Producer");  
	sp<Thread> con = new Consumer;  
	con->run("Consumer");  
  
	pro->join();  
	con->join();  
	return 0;  
}  

그리고 μ—­μ‹œλ‚˜ Androidμ—μ„œ μ œκ³΅ν•˜λŠ” classκ°€ μ‘΄μž¬ν•œλ‹€.

using namespace android;  
  
Mutex mutex;  
Condition cond;  
class Producer: public Thread  
{  
public:  
	bool threadLoop()  
	{  
		Mutex::Autolock ll(mutex);  
		for(int i=0; i<3; i++)  
		{  
			sleep(1);  
			printf("생산쀑…\n");  
		}  
		printf("μƒμ‚°μ™„λ£Œ\n");  
		cond.signal();  
				  
		return false;  
	}  
};  
class Consumer : public Thread  
{  
public:  
	bool threadLoop()  
	{  
		Mutex::Autolock ll(mutex);  
		cond.wait(mutex);  
		for(int i=0; i<3; i++)  
		{  
			sleep(1);  
			printf("μ†ŒλΉ„μ€‘β€¦\n");  
		}  
		printf("μ†ŒλΉ„μ™„λ£Œ\n");  
				  
		return false;  
	}  
};  

νŠΉμ΄ν•œ 점은 signal()κ³Ό broadcast()κ°€ μžˆλ‹€λŠ” 점이닀. broadcast APIλŠ” μ—¬λŸ¬ μ“°λ ˆλ“œμ—κ²Œ μ‹ ν˜Έλ₯Ό 보낼 수 μžˆμ–΄ νŽΈλ¦¬ν•˜λ‹€.