2009-11-26 2 views
1

가상 메서드를 사용하여 클래스 인스턴스를 만들어 pthread_create에 전달하려고하면 호출자가 파생 메서드 대신 기본 메서드를 호출하는 경우가 발생할 수있는 경쟁 조건이 발생합니다. . 검색 후 pthread vtable race, 나는 이것이 꽤 잘 알려진 행동임을 알게되었습니다. 내 질문은 주위를 둘러 보는 좋은 방법은 무엇입니까?가상 함수와 pthread_create 사이의 레이스

아래 코드는 모든 최적화 설정에서이 동작을 나타냅니다. MyThread 객체는 pthread_create에 전달되기 전에 완전히 구성됩니다.

#include <errno.h> 
#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

struct Thread { 
    pthread_t thread; 

    void start() { 
     int s = pthread_create(&thread, NULL, callback, this); 
     if (s) { 
      fprintf(stderr, "pthread_create: %s\n", strerror(errno)); 
      exit(EXIT_FAILURE); 
     } 
    } 
    static void *callback(void *ctx) { 
     Thread *thread = static_cast<Thread*> (ctx); 
     thread->routine(); 
     return NULL; 
    } 
    ~Thread() { 
     pthread_join(thread, NULL); 
    } 

    virtual void routine() { 
     puts("Base"); 
    } 
}; 

struct MyThread : public Thread { 
    virtual void routine() { 

    } 
}; 

int main() { 
    const int count = 20; 
    int loop = 1000; 

    while (loop--) { 
     MyThread *thread[count]; 
     int i; 
     for (i=0; i<count; i++) { 
      thread[i] = new MyThread; 
      thread[i]->start(); 
     } 
     for (i=0; i<count; i++) 
      delete thread[i]; 
    } 

    return 0; 
} 
+0

호기심에서 벗어난 이유는 왜 "클래스"를 '구조체'로 선언하고 있습니까? – mrduclaw

+1

구조체는 기본적으로 모든 멤버를 public으로 만드는 것을 제외하고는 동일한 것을 의미합니다. –

+1

@mrduclaw : 걱정 마세요. 그것은 최근 * class * 대신에 struct *를 사용하기 위해 최근에 관찰 한 유행입니다. 그것은 지나갈 것이다. :-P –

답변

5

여기에 유일한 문제는 양산 스레드가 메소드를 실행하기 전에 자식 소멸자가 이미 해고와 객체가 거기에 더 이상 그 시간에 의해, 그래서 당신이 개체를 삭제하는 것입니다.

그래서 pthread_create 또는 타이밍에 관계가 없으므로 스레드를 생성 할 수 없으며 리소스를 사용하고 삭제할 수 없습니다.

는 OBJS가 메인 쓰레드 전에 양산 스레드에 의해 파괴되는 방법을 보여 드리겠습니다,이 시도하는 것은 그들을 사용 : 다른 손에

struct Thread { 
pthread_t thread; 
bool deleted; 

void start() { 
    deleted=false; 
    int s = pthread_create(&thread, NULL, callback, this); 
    if (s) { 
      fprintf(stderr, "pthread_create: %s\n", strerror(errno)); 
      exit(EXIT_FAILURE); 
    } 
} 
static void *callback(void *ctx) { 
    Thread *thread = static_cast<Thread*> (ctx); 
    thread->routine(); 
    return NULL; 
} 
~Thread() { 
    pthread_join(thread, NULL); 
} 

virtual void routine() { 
    if(deleted){ 
     puts("My child deleted me"); 
    } 
    puts("Base"); 
} 
}; 

struct MyThread : public Thread { 
virtual void routine() { 

} 
~MyThread(){ 
    deleted=true; 
} 

}; 

당신이 당신이 정액을 삭제하기 전에 주에서 잠을 배치하면 스폰 된 스레드가 유효한 자원을 사용 중이므로이 문제가 발생하지 않습니다.

int main() { 
const int count = 20; 
int loop = 1000; 

while (loop--) { 
    MyThread *thread[count]; 
    int i; 
    for (i=0; i<count; i++) { 
      thread[i] = new MyThread; 
      thread[i]->start(); 
    } 
    sleep(1); 
    for (i=0; i<count; i++) 
      delete thread[i]; 
} 

return 0; 
} 
+0

고마워, 그게 다야! 좋은 캐치, 그리고 훌륭한 답변! –

+2

예. 예를 들어 절전 (1) 스레드 덕택에 스레드 [i]를 삭제하기 전에 경쟁 조건이 발생하기가 더 어려워집니다. 그러나 불가능한 것은 아닙니다. 소멸자가 호출되기 전에 스레드 루틴()이 완료되지 않거나 시작되지 않는 상황이 발생할 수 있습니다. 그래서 문제를 일으키는 부분적으로 파괴 된 객체를 사용하려고합니다. 적절한 해결책은 pthread_join에 대한 호출이 스레드가 완료되었음을 확인한 후에 만 ​​객체를 파괴하는 것입니다. –

+1

생성 된 스레드에게 충분한 시간을 주거나 자원을 삭제하기 전에 이미 작업을 완료했는지 확인하는 것은 리소스 의존성입니다. –

2

소멸자에서 pthread_join (또는 다른 실제 작업)을 수행하지 마십시오. Thread에 join() 메소드를 추가하고 main에서 thread [i]를 삭제하기 전에 호출하십시오.

소멸자에서 pthread_join을 호출하려고하면 스레드가 여전히 실행 중일 수 있습니다. Thread :: routine(). 이 이미 부분적으로 파괴 된 객체를 사용 중임을 의미합니다.. 어떻게 될까요? 누가 알아? 프로그램이 빨리 중단되기를 바랍니다. 또한


:

  • 당신이 스레드에서 상속 :: ~ 스레드가 가상 선언해야 댓글하고자하는 경우

    .

  • 모든 오류를 확인하고 적절히 처리하십시오 (소멸자 내에서 수행 할 수 없음).

+0

죄송합니다. 나는 대답을 쓰는 동안 양쪽 버전을 섞었다. 적용되지 않는 부분은 삭제했습니다. –

+0

기본 클래스 소멸자로부터 조인을 가져 오는 +1. –

+0

올바르게 수행되었다고 가정 할 때, 'join'은 생성자에서 정확하게 수행됩니다. 소멸자에서 실제 작업을하지 않는다고 말하는 것은 어리 석다. 그것은 목적을위한 것입니다. 프로그래머가 명시 적으로 정리 기능을 호출하지 않고도 특정 작업을 수행하도록 보장합니다. – jalf

관련 문제