본문 바로가기
프로그래밍/리눅스 시스템 & 네트워크 - signal

multi-thread 환경에서 signal 사용

by Noritorgigi 2013. 9. 2.
728x90

 

multithread_sig.tgz

일반적으로 multi-thread 환경에서는 signal을 잘 사용하지 않지만, 불가피하게 signal을 사용하게 되는 경우가 생길 수 있다. 하지만 signal의 특성상 signal은 특정 process를 지정해서 보내는 것이지 특정 thread로 전달되는 것이 아니기 때문에 process 내의 어떤 thread에서 signal을 받아 처리할지 모른다. 예를 들어 main thread와 thread1, thread2가 있다고 가정하면 main thread에서 signal handler를 지정하더라도, signal handler에서 처리되지 않고, thread1 혹은 thread2로 signal이 전달될 수 있다. 그러므로 이 경우에는 다음 그림과 같은 특별한 처리가 필요하다.  

 

 

 

 

그림에 관해서 간략히 설명하겠다. 먼저 main thread에서 pthread_sigmask API를 이용해 SIGINT를 제외한 모든 signal을 mask시킨다.  다음으로 signal processing 전용 thread와 그 외의 worker thread를 생성한다. signal processing 전용 thread에서는 sigwait API를 이용해 수신하고자하는 signal만 수신한다. 그 외의 worker thread들은 자신들을 생성한 main thread의 signal mask속성을 그대로 따라가므로 signal 수신없이 정상적으로 작업을 수행할 수 있다. 이 때 유의할 사항은 main thread에서 반드시 signal mask 설정을 한 뒤, worker thread를 생성해야 한다.

다음의 코드는 위의 그림을 간략하게 구현한 것이다.  코드 속의 signal processing thread는 startSigThread, worker thread는 startThread이다. 설명은 코드 속의 주석을 참조하기 바란다. 해당 코드 컴파일 후, 실행한 뒤,

kill -SIGUSR1 pid

와 같은 방식으로 명령을 실행하면, signal handler함수가 호출될 것이다. linux에서 컴파일해본 경험이 없다면 첨부파일을 참조하기 바란다.

 multithread_signal.c

 #include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>

#define NUM_THREADS 3


typedef struct _thread_args
{
 pthread_t tId;
 void* (*func)();
}thread_args;

void *startSigThread(void*); // signal processing thread func
void *startThread(void*);    // worker thread func

void cleanThread(thread_args* tArgs); // thread 종료 담당
void sa_handler_usr(int nSigNum);     // signal handler func

thread_args tThreadArgs[NUM_THREADS] = {
 {0, startSigThread},
 {0, startThread},
 {0, startThread}
};

int main()
{
 int i;
 int nRtn;
 sigset_t tSigSetMask;

 //signal set setting 및 sigmask
 sigfillset(&tSigSetMask);
 sigdelset(&tSigSetMask, SIGINT);
 pthread_sigmask(SIG_SETMASK, &tSigSetMask, NULL);
 printf("*Process PID = %d\n", getpid());

 // thread 들 생성
 for(i = 0; i < NUM_THREADS; i++)
 {
  nRtn = pthread_create(&tThreadArgs[i].tId, NULL, tThreadArgs[i].func, (void*)&tThreadArgs[i]);
  if(nRtn == -1)
  {
   perror("pthread create error\n");
   return -1;
  }
  printf("Create thread : tid = %lu\n", tThreadArgs[i].tId);
 }

 // thread 종료
 cleanThread(tThreadArgs);

 return 0;
}

void *startSigThread(void* pArgs)
{
 thread_args* tArgs = (thread_args*)pArgs;
 sigset_t tSigSetMask;
 int nSigNum;
 int nErrno;

 printf("* Start signal thread (tid = %lu)\n", (long)pthread_self());
 // 수신할 signal set setting
 sigemptyset(&tSigSetMask);
 sigaddset(&tSigSetMask, SIGUSR1);
 sigaddset(&tSigSetMask, SIGUSR2);
 sigaddset(&tSigSetMask, SIGTERM);
 
 while(1)
 {
  // signal 대기
  nErrno = sigwait(&tSigSetMask, &nSigNum);
  if(nErrno >  0)
  {
   perror("sigwait error\n");
   return NULL;
  }
  // signal no. 에 따라 signal handler 함수 호출 혹은 종료
  switch(nSigNum)
  {
   case SIGUSR1:
   case SIGUSR2:
    sa_handler_usr(nSigNum);
    break;
   case SIGTERM:
    printf("[signal SIGTERM]\n");
    exit(EXIT_SUCCESS);
   default:
    break;
  }

 }
 return tArgs;
}

void *startThread(void* pArgs)
{
 thread_args* tArgs= (thread_args*)pArgs;
 while(1)
 {
  sleep(1);
 }
 return tArgs;
}

void cleanThread(thread_args* pArgs)
{
 thread_args* tArgs;
 int i;

 for(i = 0; i < NUM_THREADS; i++)
 {
  pthread_join(pArgs->tId, (void**)&tArgs);
  printf("Thread id(%lu) joined\n", pArgs->tId);
  pArgs++;
 }
}

void sa_handler_usr(int nSigNum)
{
 printf("\t[%lu] Signal(%s)\n",
   pthread_self(),
   nSigNum == SIGUSR1? "USR1":"USR2");
}

코드 출처 : advanced 리눅스 시스템 네트워크 프로그래밍(2판)

728x90