pthread 에서 pthread_cond_signal() 이 먹통이 되어 버리는
글쓴이: jinoos / 작성시간: 화, 2004/06/15 - 7:59오후
쓰레드 풀을 짜보고 있습니다. C++ 쓰레드 콜백 함수만 빼고 나머지는 하나의 클래스로 만들었습니다.
객체를 생성할때 쓰레드의 갯수를 입력받아 malloc로 공간을 할당하고 stat 에 값을 넣어 동작중인지 아닌지를 판단해서 run() 함수가 실행할때 앞쪽부터 검사하는 방법을 사용하고 있습니다.
문제는 pthread_cond_wait() 된 쓰레드들중에 0번 쓰레드를 제외한 나머지 쓰레드는 pthread_cond_signal() 으로 깨어나질 못한다는데 있습니다.
골치 아프네요. 이것저것 모아서 거의다 되어가는데 결정적인 문제가 :cry:
아래 코드는 하나의 파일로 묶은 것이고 바로 컴파일 될껍니다. 실행코드까지 넣었으니 테스트 부탁 드립니다.
아참 참고로 FreeBSD환경에서 -pthread로 컴파일 해서 작업중입니다.
#include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #define THREAD_DATA_SIZE 256 #define THREAD_ENABLE 0 #define THREAD_DISABLE 1 void* threadPool(void *data); typedef struct _thr_info { pthread_t *thread; pthread_mutex_t *mutex; pthread_cond_t *cond; char *data; int stat; int serial; } thr_info; class ThreadPool { public: ThreadPool(int); ~ThreadPool(); int init(); int run(char *); private: int thr_size; // 쓰래드 갯수 thr_info *thrque; void initThrInfo(thr_info&, int); void freeThrInfo(thr_info &); }; // 생성자에 생성할 쓰레드 풀의 갯수를 인자로 넣는다. ThreadPool::ThreadPool(int thread_size) { thr_size = thread_size; thrque = NULL; } ThreadPool::~ThreadPool() { for(int i=0; i<thr_size; ++i) { freeThrInfo(thrque[i]); } free(thrque); } // 쓰레드 자원을 할당한다. int ThreadPool::init() { // 메모리를 할당 한다. thrque = (thr_info*) malloc(sizeof(thr_info)*thr_size); if(thrque == NULL) { return -1; } for(int i=0; i<thr_size; ++i) { initThrInfo(thrque[i], i); } return 0; } // 외부에서 쓰레드에게 데이터를 전달하고 쓰레드 동작을 // 시키는 함수 int ThreadPool::run(char *data) { thr_info *thr; char buf[1024]; strncpy(buf, data, 1023); while(1) { for(int i=0; i<thr_size; ++i) { thr = &(thrque[i]); if(thr->stat == THREAD_ENABLE) { printf("RUN : get sleep thread #%d\n", thr->serial); pthread_mutex_unlock(thr->mutex); pthread_mutex_lock(thr->mutex); thr->stat = THREAD_DISABLE; strncpy(thr->data, data, THREAD_DATA_SIZE-1); printf("( %d : %d : %d )\n", thr->serial, thr->mutex, thr->cond); pthread_mutex_unlock(thr->mutex); pthread_cond_signal(thr->cond); return 0; } } usleep(10000); } } // 실제 동작되는 프로세스 void ThreadPool::initThrInfo(thr_info &thri, int pos) { thri.thread = NULL; thri.thread = NULL; thri.mutex = NULL; thri.cond = NULL; thri.data = NULL; thri.thread = (pthread_t*) malloc(sizeof(pthread_t)); thri.mutex = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t)); thri.cond = (pthread_cond_t*) malloc(sizeof(pthread_cond_t)); thri.data = (char *) calloc(sizeof(char)*THREAD_DATA_SIZE, 0x00); thri.serial = pos; pthread_mutex_init(thri.mutex, NULL); pthread_cond_init(thri.cond, NULL); printf("( %d : %d : %d )\n", thri.serial, thri.mutex, thri.cond); pthread_create(thri.thread, NULL, &threadPool, (void *)&thri); } // 자원을 thr_info 내부에 할당된 자원을 free한다. void ThreadPool::freeThrInfo(thr_info &thri) { if(thri.thread != NULL) free(thri.thread); if(thri.mutex != NULL) free(thri.mutex); if(thri.cond != NULL) free(thri.cond); if(thri.data != NULL) free(thri.data); } void* threadPool(void *data) { thr_info *thr = (thr_info*)data; while(1) { thr->stat = THREAD_ENABLE; pthread_mutex_lock(thr->mutex); pthread_cond_wait(thr->cond, thr->mutex); // 0번 쓰레드를 제외한 pthread_mutex_unlock(thr->mutex) // 다른 스레드들은 모두 이곳에서 block 되어 // 깨어나지 못합니다. //////////////////////////////////////////////// // 실제 필요한 동작이 들어가는 부분.. //////////////////////////////////////////////// // 동작이 요청보다 느리게 하기 위해서 sleep sleep(2); // 받아온 데이터를 출력 // serial에는 쓰레드 번호가 들어가 있다. printf("#%d : data : %s \n",thr->serial, thr->data); fflush(stdout); //////////////////////////////////////////////// } } int main() { char buf[1024]; ThreadPool tp(3); // 3개의 쓰레드를 가지는 pool을 생성. sleep(1); tp.init(); sleep(1); for(int i =0; i< 1000; ++i) { sprintf(buf, "#%d", i); tp.run(buf); // 쓰레드를 동작 sleep(1); // 1초 간격으로 처리를 요청한다. } return 0; }
Forums:
pthread_mutex_t 와 pthread_cond_t 를 바로 사용
pthread_mutex_t 와 pthread_cond_t 를 바로 사용하지 않은 이유가 무엇인가요? 굳이 포인터 타입으로 해서 각각 다시 할당 받을 필요가 없습니다.
#define PTHREAD_MUTEX_INITIALIZER NULL
#define PTHREAD_COND_INITIALIZER NULL
#define PTHREAD_RWLOCK_INITIALIZER NULL
이렇게 초기값을 주는 이유도 한번 알아보시구요. 이렇게 해서 *_init 함수들이 알아서 할당해주도록 하는 것이 저는 더 편하고 의미상으로도 잘 통했습니다. 그리고, 그 구조체의 thread 멤버도 포인터 타입일 필요가 없습니다. pthread* 함수를 호출할 때 & 를 덜 사용하시려고 그렇게 한 것인가요? & 를 사용하는 것이 보기에 더 안전하다고 생각합니다.
제가 고친 것은 포인터를 사용하지 않게 한 것이 다구요. 외견상 각 쓰레드가 cond_signal 을 넘겨 받아 대기 상태를 풀고 동작하는 것을 확인하였습니다. (쓰레드 함수 안에서 #번호가 0, 1, 2: data 이렇게 돌아가면서 찍히면 되는거죠?) 프비 4.9에서 확인한 것입니다. 실제 원하는 데이터 조작은 확인해보지 않았습니다.
몇가지 더 말씀드리고 싶은 것은... 여전히 lock 후 unlock 이 확실하게 된다는 보장이 필요하고 (이전 게시물에 몇개가 있습니다. 저는 ScopeGuard 를 사용합니다.) 또 lock / unlock 개수가 틀린 부분도 있습니다. 그리고, singal 과 wait 시에 lock / unlock 의 위치가 제가 보기에 조금 틀어진 것 같습니다. 마지막으로 stat 를 억세스할 때에도 lock / unlock 이 필요합니다.
쓰레드 풀의 많은 예제들이 있습니다. 다른 라이브러리를 꼭 참고해보시고 구현에 참고하시면 많은 도움이 되리라 생각합니다. 쓰레드 풀을 사용하는 방법이 다양하다보니 일반화 하기가 좀 까다롭기는 하겠지만 많은 고민을 한 후에 클래스 인터페이스를 설계하시고, 더 나아가서 템플릿화 할 수 있다면 훨씬 더 좋은 상위 쓰레드 라이브러리가 되리라 기대합니다.
[quote="bugiii"]pthread_mutex_t 와 pthrea
일단 얘기 해주신 대로 초기화로 당면한 문제는 해결했습니다. C++이나 쓰레드가 익숙하지 않아 문제점이 많습니다. :oops:
포인터는 객체 생성시 생성자에서 지정한 쓰레드 사이즈로 풀을 생성하기 위해서 사용했는데 다른 방법이 있다면 어떤 방법이 있는지 귀뜸이라도 해주시면 감사하겠습니다...
동작은 말씀하신대로 0,1,2 가 돌면서 외부에서 입력받은 문자열을 출력하는게 맞습니다. 지금 테스트 해보니 계속 문제점이 보이네요.. ScopeGuard의 내용이 나온글을 보았습니다. 정신없어 확인 못해 봤는데 내일 출근하면 점더 본격적으로 공부를 해야겠습니다.
설계부터 좀더 고민하면서 고쳐 나가겠습니다. 오늘 테스트 감사드립니다.
목적을 찾아서... jiNoos
제가 착각을 한 것이 하나 있었습니다. 집 나와서 PC 방에서 생각나는대
제가 착각을 한 것이 하나 있었습니다. 집 나와서 PC 방에서 생각나는대로 쓰다보니... (집에서는 와이프가 PC 를 만지지 못하게 합니다... 우웅...)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock( &mutex );
// 어쩌구 저쩌구
pthread_mutex_unlock( &mutex );
이런식이면 충분합니다. pthread_mutex_init 이 필요없습니다. _lock / _unlock 함수 에서 mutex 가 PTHREAD_MUTEX_INITIALIZER 이면 알아서 내부적으로 pthread_mutex_init 을 호출합니다. 그리고 저는 pthread_mutex_init 은 한번도 사용해본 적이 없습니다.
쓰레드 풀 생성자에서 초기값으로 개수만큼 구조체를 할당하시려고 한다면, 그냥 포인터 없이 사용하시고 그 구조체에 디폴트 생성자를 이용하시는 것도 한가지 방법이 될 수 있을 것입니다. 그냥 생성자 초기화 리스트에 PTHREAD_MUTEX_INITIALIZER 를 주면 되는 것 아니겠습니까...
다만, pthread_mutex_t 가 내부의 구조를 감추기 위해서, 또 초기화를 편하게 하기 위해서 등의 이유로 포인터 타입으로 사용하는 경우가 대부분이겠지만, 임베디드 같은 환경에서는 구조체를 날로 그냥 사용하는 경우도 있지 않을까 예상해볼 수 있습니다. (그런 곳에는 동적 메모리 할당 자체가 없을 수 있고 해서... 순전히 제 추측일 뿐입니다.) 그런 경우에는 생성자의 초기화 리스트에 나열할 수 없는 문제점도 있기는 할 것 같습니다. (이것은 Gnu Pth 가 그렇기 때문에 생각해본 것입니다. 물론 pth 의 pthread 에뮬레이션에의 mutex 타입은 일반적인 포인터 타입입니다.)
pthread_mutex_t 의 타입을 pthread 가 강제하지는 않는 것 같습니다. 그러니 이점은 유의해야 할 것 같습니다.
pthread_mutex_init 을 명시적으로 사용해야 하는 경우라면, mutex 의 속성을 줘야 하는 경우 정도 아닐까 생각합니다. (mutex 가 만들어진 후에 속성 지정이 되나요?)
그리고, 현재 사용할 수 있는 쓰레드를 판별하는 것도 처음부터 검사하는 것보다는 약간의 수고를 더해서 큐 정도로 해결하시면 편하지 않을까 생각해봅니다. 아니면 프리 리스트도 괜찮을 듯 하구요. 캐쉬 적중율을 높힌다는 차원에서 (아주 약간이기는 하겠지만) 스택을 쓸 수도 있지 않을까요? 최근에 사용한 것을 다시 사용하는 전략 정도...
또... 개인적인 취향이랄 수도 있겠지만 malloc 이나 new 를 사용해야 하는 경우가 있다면 저는 std::vector 같은 것을 사용할 수 있지 않을까 하고 한번 더 생각합니다. C++ 로 프로그래밍한다면 STL은 (적극적으로) 사용해볼만 하다고 생각합니다. 그리고, 위의 구조체 생성자는 malloc 해서는 호출할 수 없으니까 new 나 vector 를 어차피 사용해야 할 것 같습니다.
마지막으로, mutex, cond_var 등의 리소스 해제는 pthread_*_destroy 를 사용하셔야 합니다.
쓰레드 풀, DB 커넥션 풀, 메모리 풀 등을 풀이라는 템플릿으로 일반화 해볼 수 있는지도 시간나면 한번 고민해 보고 싶은 것중에 하나입니다.
어제 소스를 짜보고 집에 가면서 "Effecient C++" 책의 "생성
어제 소스를 짜보고 집에 가면서 "Effecient C++" 책의 "생성자와 소멸자" 챕터를 보고 좀더 감이 슬슬 오네요. :lol:
제가 잠간 의문이 든것이 있는데 뮤텍스의 경우 pthread_mutexattr_t을 사용하지 않는 초기화는 별로 의미가 없다고 하셨는데 그렇다면 의미가 없지만 init 해줘도 무방하지 않을까요? 헌데
- pthread_mutex_init()로 초기화한것과
- PTHREAD_MUTEX_INITIALIZER 로 초기화건것이
어떻게 다른지 그리고 왜 ~~init() 함수를 사용했을때는 동작이 문제가 되었는지 혼란을 느낍니다. 아래 문서링크에서는 또 malloc의로 동적으로 할당한 mutex는 init() 함수로 초기화 하라고 했는데 제 코드에서 동작은 아래 문서가 틀렸다는 얘기가 되는데요.. 이것역시 혼란의 반복이네요 :cry:
http://www-903.ibm.com/developerworks/kr/linux/library/l-posix2.html
아참 그리고 리눅스와 프비에서 번갈아 가면서 작업을 하고 있는데 PTHREAD_MUTEX_INITIALIZER 는 리눅스에서 define하지 않아되 되는데 리눅스에서는 모양이 약같 틀린것 같습니다. 리눅스에서는 PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER 가 Macro 인가요?
잼있고도 복잡하군요. C++ ^^
목적을 찾아서... jiNoos
[code:1]int ThreadPool::run&
이 함수가 잘못되었군요.
unlock -> lock -> unlock -> signal 순서로 동작하고 있습니다.
bugiii님이 지적하신 데로 unlock이 하나 많다는 것과, signal의 위치가 잘못되었습니다.
signal은 lock을 잡은 상태에서 불러야 합니다.
그리고 unlock보다는 lock이 앞에 있어야 하겠죠?
lock -> signal -> unlock으로 하시면 잘 동작할 껍니다.
그리고 또 한가지는 아마 usleep는 thread safe한 함수가 아닐 껍니다.(Solaris 8에서는 그렇습니다.)
thread safe하지 않기 때문에 usleep에서 안깨어나는 경우가 발생할 수도 있습니다.
사용하시는 OS의 usleep의 thread safe여부도 조사해 보세요.
[quote="jinoos"]어제 소스를 짜보고 집에 가면서 "Effec
먼저 PTHREAD_MUTEX_INITIALIZER 가 어떤 것인지 알아서는 안됩니다. 이것이 NULL 이 될지 아니면 특정 static 구조체 변수일지 아니면 매직 값일지 아니면 구조체 초기화하는 매크로일지 아무도 알아서는 안됩니다.
그리고, jinoos님의 혼동을 저도 어제밤에 느꼈습니다. 그래서 프비 pthread 에 해당하는 uthread 소스를 뒤져가며 봤는데도... 답을 못찾아서 포인터 없는 형태로 해결책을 제시한 것입니다. pthread_mutex_t 를 그냥 사용한 경우 _init 을 하거나 말거나 잘 동작합니다. 하지만 포인터 타입을 쓰고 malloc 한 것으로 _init 를 하면... 흠... 안될리가 없었습니다...
그래서... (아침까지 찜찜한 마음도 있고 또 혼동을 말씀하시기에) 다른데 문제가 없나 다시 한번 뚫어지라 살펴보았습니다. 정말... 점심때 순대국밥 먹으러 갔는데, 거기까지 프린트해서 가져갔습니다. 순대국밥집이 어떤지 아시죠? 북적북적... 결국 원인은 다른 곳에 있었습니다.
calloc ... 호출을 잘못하셨습니다... 두번째로 넘긴 인자를 잘 보세요. 그것 때문에 이상하게 뮤텍스 보관할 장소가 침범당하는지... mutex와 cond_var 로써 동작을 못하는 것처럼 행동하였습니다. (더이상 생각하기에는 눈도 아프고 머리도 아픕니다. -_-; 왜 그런지 더 찾아봐주세요.)
하여튼 예상대로 malloc 한 것의 포인터 타입도 _init 잘 되구요. 잘됩니다. 아... _init 를 사용하는 현실적인 예제가 하나 있길래 말씀드리면... UNP 에 보면 27장 9절인가에 프로세스끼리 뮤텍스 공유하는 속성줄 때, 이 뮤텍스를 공유메모리에 놓고 _init 하는게 하나 있더군요. 그럴 때 사용할 수 있겠다 싶지만 현재는 Solaris 에서만 프로세스간 뮤텍스 공유가 되니까... 프비에는 해당 사항이 없겠습니다.
p.s. 할일도 많고 바쁜데 KLDP 때문에 정말 죽겠습니다... 일이 하기 싫어서 그런 것이 아니라구요.... :twisted:
[quote="kimsk99"]그리고 또 한가지는 아마 usleep는 t
조사해 보았는데요. 프비의 usleep 은 nanosleep 으로 구현되어 있고 기본 pthread 인 uthread 에서 nanosleep 은 쓰레드 안전으로 만들어져 있었습니다. 프비만 있어서 리눅스는 모르겠습니다.
댓글 달기