2010-08-22 2 views
0

저는 pthread를 사용하여 실험하고있는 아마추어 프로그래머로서, 멀티 스레드 프로그램이 어느 정도 오랫동안 계산할 때 효율성을 높일 수 있는지를 확인합니다. 계산은 std :: list < string> object를 통해 실행되며 목록의 첫 번째 요소가 꺼지고 그걸로 무언가를 계산하는 스레드로 전달됩니다. 이 프로그램은 활성 스레드를 추적하고 항상 활성 스레드가 실행 중인지 확인합니다. 목록이 비면 프로그램은 결과 데이터를 정렬하고 데이터 파일을 덤프하고 종료합니다.pthread를 사용하는 단순한 보스 - 작업자 모델

프로그램의 멀티 스레드 버전은 현재 작동하지 않습니다. 목록에서 20 또는 40 또는 200 정도의 요소를 가져옵니다 (목록에 따라 달라집니다). segfaults. segfault는 목록의 특정 요소에서 발생하며 임의의 방식으로 임의로 나타나지 않는다는 것을 의미합니다.

BUT 디버그 기호로 컴파일하고 gdb를 통해 프로그램을 실행하면 이상한 것은 프로그램이 segfault하지 않습니다. 그것은 완벽하게 실행됩니다. 천천히, 물론,하지만 그것은 실행하고 내가 기대하는대로 모든 것을 않습니다.

valgrind의 도구를 사용하여 잠시 동안 모든 사람의 의견을 듣고 놀고 난 후에 무슨 일이 일어나고 있는지 알아 봅니다. 아래의 간단한 코드 (std 라이브러리 또는 pthread 라이브러리 외부의 호출없이)가 helgrind에 문제를 일으키고 이것이 내 문제의 원인 일 가능성이 있음을 알았습니다. 여기 단순화 된 코드와 helgrind의 불만이 있습니다.


==2849== Helgrind, a thread error detector 
==2849== Copyright (C) 2007-2009, and GNU GPL'd, by OpenWorks LLP et al. 
==2849== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info 
==2849== Command: ./thread2 2 6 
==2849== 
Program will have 2 threads. 
==2849== Thread #2 was created 
==2849== at 0x64276BE: clone (clone.S:77) 
==2849== by 0x555E172: [email protected]@GLIBC_2.2.5 (createthread.c:75) 
==2849== by 0x4C2D42C: pthread_create_WRK (hg_intercepts.c:230) 
==2849== by 0x4C2D4CF: [email protected]* (hg_intercepts.c:257) 
==2849== by 0x401374: main (in /home/rybu/prog/regina/exercise/thread2) 
==2849== 
==2849== Thread #1 is the program's root thread 
==2849== 
==2849== Possible data race during write of size 8 at 0x7feffffe0 by thread #2 
==2849== at 0x4C2D54C: mythread_wrapper (hg_intercepts.c:200) 
==2849== This conflicts with a previous read of size 8 by thread #1 
==2849== at 0x4C2D440: pthread_create_WRK (hg_intercepts.c:235) 
==2849== by 0x4C2D4CF: [email protected]* (hg_intercepts.c:257) 
==2849== by 0x401374: main (in /home/rybu/prog/regina/exercise/thread2) 
==2849== 
[0 start.] [1 start.] 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 [0 done.] [1 done.] [2 start.] [3 start.] 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 [2 done.] [3 done.] [4 start.] [5 start.] 4 5 4 5 4 5 4 5 4 5 4 5 4 5 4 5 4 5 4 5 [4 done.] [5 done.] ==2849== 
==2849== For counts of detected and suppressed errors, rerun with: -v 
==2849== Use --history-level=approx or =none to gain increased speed, at 
==2849== the cost of reduced accuracy of conflicting-access information 
==2849== ERROR SUMMARY: 6 errors from 1 contexts (suppressed: 675 from 37)

#include <cstdlib> 
#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string> 
#include <list> 
#include <iostream> 
#include <signal.h> 
#include <sys/select.h> 

struct thread_detail { 
pthread_t *threadID; 
unsigned long num; 
}; 

pthread_mutex_t coutLock; 

void *ThreadToSpawn(void *threadarg) 
{ 
    struct thread_detail *my_data; 
    my_data = (struct thread_detail *) threadarg; 
    int taskid = my_data->num; 

    struct timeval timeout; 
    for (unsigned long i=0; i < 10; i++) 
    { 
    timeout.tv_sec = 0; timeout.tv_usec = 500000; // half-second 
    select(0, NULL, NULL, NULL, & timeout); 
    pthread_mutex_lock(&coutLock); 
    std::cout << taskid << " "; std::cout.flush(); 
    pthread_mutex_unlock(&coutLock); 
    } 
    pthread_exit(NULL); 
} 


int main (int argc, char *argv[]) 
{ 
    unsigned long comp_DONE=0; 
    unsigned long comp_START=0; 
    unsigned long ms_LAG=10000; // microsecond lag between polling of threads 

    // set-up the mutexes 
    pthread_mutex_init(&coutLock, NULL); 

    if (argc != 3) { std::cout << "Program requires two arguments: (1) number of threads to use," 
           " and (2) tasks to accomplish. \n"; exit(1); } 
    unsigned long NUM_THREADS(atoi(argv[1])); 
    unsigned long comp_TODO(atoi(argv[2])); 
    std::cout << "Program will have " << NUM_THREADS << " threads. \n"; 
    std::list <thread_detail> thread_table; 

    while (comp_DONE != comp_TODO) // main loop to set-up and track threads 
    { 
    // poll stack of computations to see if any have finished, 
    // extract data and remove completed ones from stack 
    std::list <thread_detail>::iterator i(thread_table.begin()); 
    while (i!=thread_table.end()) 
     { 
     if (pthread_kill(*i->threadID,0)!=0) // thread is dead 
     { // if there was relevant info in *i we'd extract it here 
     if (pthread_join(*i->threadID, NULL)!=0) { std::cout << "Thread join error!\n"; exit(1); } 
     pthread_mutex_lock(&coutLock); 
     std::cout << i->num << " done. "; std::cout.flush(); 
     pthread_mutex_unlock(&coutLock); 
     delete i->threadID; 
     thread_table.erase(i++); 
     comp_DONE++; 
     } 
     else (i++); 
     } 
    // if list not full, toss another on the pile 
    while ((thread_table.size() < NUM_THREADS) && (comp_TODO > comp_START)) 
     { 
     pthread_t *tId(new pthread_t); 
     thread_detail Y; Y.threadID=tId; Y.num=comp_START; 
     thread_table.push_back(Y); 
     int rc(pthread_create(tId, NULL, ThreadToSpawn, (void *)(&(thread_table.back())))); 
     if (rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } 
     pthread_mutex_lock(&coutLock); 
     std::cout << comp_START << " start. "; std::cout.flush(); 
     pthread_mutex_unlock(&coutLock); 
     comp_START++; 
     } 

    // wait a specified amount of time 
    struct timeval timeout; 
    timeout.tv_sec = 0; timeout.tv_usec = ms_LAG; 
    select(0, NULL, NULL, NULL, & timeout); 
    } // the big while loop 

    pthread_exit(NULL); 
} 

Helgrind 출력은 아마 내가 잘못된 방법으로의 pthreads를 사용하고 있지만, 내가 잘못 뭘하는지 나에게 그렇게 분명하지 않다. 더욱이 헬 그린 트 출력을 어떻게 만들지는 확실하지 않습니다. 이전 helgrind는 내가 코드가 죽었다는 것을 알게 된 다른 이유로 스레드에서 pthread_join을 호출하지 않았기 때문에 불평을했습니다. pthread_join을 추가하면 이러한 불만 사항을 처리했습니다.

온라인으로 다양한 pthread 자습서 읽기 위의 코드 에서처럼 스레드 생성 및 삭제 작업을 많이하는 것은 의미가 없다는 것을 발견했습니다. 아마도 N 개의 스레드가 동시에 실행되고 뮤텍스와 공유 메모리를 사용하여 "BOSS"스레드와 "WORKER"스레드간에 데이터를 전달하고 프로그램 종료시 WORKER 스레드 만 한 번만 죽이는 것이 더 효율적입니다. 그래서 결국 조정해야 할 것이지만 위의 코드에는 분명히 잘못된 점이 있습니까?

편집 : 일부 키워드는 점점 더 자주 나타납니다. 내가 만들려고하는 용어는 분명히 스레드 풀입니다. 게다가 boost :: threadpool, boost :: task, boost :: thread 등 boost 라이브러리에있는 표준 구현에 대한 다양한 제안이 있습니다. 이들 중 일부는 제안 일뿐입니다. 나는 여기 사람들이 you can combine ASIO and boost::thread을 언급하면서 내가 찾고있는 것을 성취 할 수있는 실을 발견한다. 마찬가지로 메시지 큐 클래스가 있습니다.

흠, 그래서 나는 많은 사람들이 요즘 생각하고있는 주제의 표면을 긁어 모으고있는 것처럼 보이지만, OOP가 1989 년이나 무언가와 같았습니다.

+0

코드가 너무 많습니다./코드가 충분하지 않습니다. 여전히 문제가있는 최소한의 샘플로 줄이면 처리 과정에서 오류를 찾을 수 있습니다. –

+1

helgrind가 식별 한 모든 종족을 고친 다음 다시 시도하십시오. – caf

+1

당신이 stdio ('std :: cout')에 동시에 접근하려고 시도하고 있기 때문에 현재 테스트 프로그램에서 볼 수있는 대부분의 경주가 있습니다. – caf

답변

1

위로 가기 몇개의 스레드를 사용하고 있습니까?내 최고 출력에 데이터가 표시되지 않지만 스레드를 사용할 때 가상 열 풍선을 보았습니다. 내 이해 (그리고 아마도 내가 물어 봐야 겠어) 각 스레드는 잠재적으로 사용할 수있는 자체 메모리 공간을 가지고있다. 이 메모리는 실제로 사용되지 않고 필요에 따라 사용할 수 있습니다. 따라서 실제로 문제를 일으키지 않고도이 수치가 상당히 높아질 수 있습니다. 자체적으로 그리고 기억 자체는 아마도 재앙이 아닙니다. DATA 사용률이 사용중인 스레드 수와 선형으로 조정되는지 확인해야합니다.

gdb 관련. 앞에서 언급했듯이 gdb는 코드를 수정하지 않고 메모리를 손상시키는 경우 오류가 발생한 위치를 이동할 수 있습니다. 당신이 돌아 가지 않거나 이미 풀어 놓은 상태에서 부패가 발생하여 재사용을 시도하지 않으면 문제의 증상이 사라집니다. 중요한 일부 영역에서 코드를 데모하거나 사용해야 할 때까지 이동하십시오.

또한 valgrind의 일부인 helgrind을 살펴볼 수 있습니다.

Helgrind는 C, C에서 동기화 오류를 감지 ++ 및 프리미티브를 스레딩 POSIX의의 pthreads를 사용 포트란 프로그램에 대한 Valgrind의 도구입니다 : 당신은 잠금 문제가있는 경우 이런 종류는 빵과 버터입니다.

그냥 할 : 당신은 전체 코드 확신

valgrind --tool=helgrind {your program} 
+0

이상하게도 프로그램을 사용하는 스레드의 수에 관계없이 이상하게 발생합니다. 1, 2, 10. 항상 풍선. –

+0

helgrind 팁을 보내 주셔서 감사합니다. 모든 디버깅 도구에 대해 알아두면 좋을 것입니다. 다양한 "가능한 데이터 경주"상황에 대한 많은 수분이 많은 데이터를 제공합니다. –

+0

helgrind가 실행 중일 때 segfault가 발생하지 않습니다. Helgrind가 실행되지 않았 으면 애플리케이션이 충돌 할 상황에서 helgrind 출력을 표시하기 위해 원래 게시물을 편집합니다. –

2

코어 덤프 (ulimit -c unlimited)를 활성화 한 다음 gdb없이 프로그램을 실행하십시오. 크래시가 발생하면 코어 파일을 남겨두고 gdb를 열고 조사를 시작합니다 (gdb <executable-file> <core-file>).

+2

@Ryan : backtrace 명령'bt'는 segfault를 트리거 한 * your * 코드의 행을 볼 수있게합니다. – caf

+1

@ Ryan : 해당 클래스의 인스턴스가 스레드간에 공유됩니까? 스레드로부터 안전합니까? 그렇지 않은 경우 해당 클래스에 대한 연산을 잠그는 기능을 추가해야합니다. – caf

+0

아니요, 인스턴스가 공유되지 않습니다. 클래스는 개별 스레드에서 초기화되며 다른 스레드가 존재한다는 것을 모릅니다. –

1

있습니까? 어디에서 스레드를 만들지 또는 BuildKCData를 호출하는 위치를 알 수 없습니다.

pthread_kill() 이후에 메모리 장벽을 가져야합니다.이 경우에는 차이가 나는 것 같지는 않습니다.

편집 : 순차적 실행과 캐시 일관성이 혼란 스럽습니다.

캐시 일관성 : 86 (현재) 4 바이트 액세스 원자이다 정렬을 보장하므로 스레드 B 스레드 A의 a[0]=123a[1]=456는 작동 - 스레드 C는 결국 "123456"을 참조한다. 캐시 일관성 프로토콜에는 여러 가지가 있지만 대략 MRSW 잠금이라고 생각합니다.

순서가 잘못된 실행 : x86은 읽기 순서를 보장하지 않으며 Linux 커널에서 sfence가 필요한지 여부에 대한 논란이있었습니다. 이렇게하면 CPU가 데이터를 더 효과적으로 프리 페치 할 수 있지만 스레드 A에서 a[0]=123,a[1], 스레드 B에서 a[1]=456,a[0]이 모두 0을 반환 할 수 있습니다. [1]의 페치가 [0]로드 전에 발생할 수 있기 때문에 0을 반환 할 수 있습니다. 이 문제를 해결하는 일반적인 방법은 두 가지가 있습니다.

  • 자물쇠를 잡고있을 때만 공유 데이터에 액세스하십시오. 특히, 잠금 외부에서 공유 데이터를 읽지 마십시오. 이것이 각 항목에 대한 잠금 또는 전체 배열에 대한 잠금을 의미하는지 여부는 귀하에게 달려 있으며 잠금 경합이 유사 할 것으로 생각하는 내용 (팁 : 일반적으로 그렇게 크지 않습니다).
  • 순서대로 있어야하는 것 사이에 메모리 장벽을 둡니다. 이것은 올바르게하기가 어렵습니다 (pthread는 심지어 메모리 장벽조차 가지고 있지 않습니다; pthread_barrier은 동기화 지점과 더 비슷합니다). 메모리 장벽이 최근의 추세 동안

는 잠금이 훨씬 쉽게 제대로하기 위해 (내가 잠금을 보유하고 을, 다른 그러므로 아무도 내 발에서 데이터를 변경할 수 없음) 입니다.메모리 장벽은 일부 서클 모든 분노하지만, 더 많은 오 내가 내가 다른 스레드가 차단를 사용 희망 내가 다른 스레드가 원자 쓰기 희망이 읽기 원자 희망 (를 바로 얻을하고있다 예, 장벽을 사용해야합니다.).

잠금이 너무 느리면 잠금 장치를 장벽으로 바꾸고 올바른 결과를 얻으려는 것보다 경합을 줄이는 것이 훨씬 효과적입니다.

+0

"메모리 장벽"이란 무엇입니까? 당신이 무엇을 얻고 있는지 확신하지 못합니다. 당신이 무슨 뜻인지 말해 주면, 시도해보기가 행복 할 것입니다. BuildKCData가 호출되고 있습니다. while 루프에서 comp_START ++로 끝납니다. 위쪽에서 아래쪽으로 읽는 중, std :: cout << ".. .. sorting .."; 요구. –

+0

오, 이제 무슨 뜻인지 알 겠어. 어떤 형식의 문제가 있습니다. 코드가 있지만 코드 딱지에서 꺽쇠 괄호가 이상하게 취급됩니다. 내가 고칠 수 있는지 알게 될거야. –

+0

템플릿 호출과 같은 것보다 작은 꺽쇠 괄호로 보였습니다. 기호보다 작 으면 코드 서식이 엉망이되었습니다. 나는 다소 고쳐졌지만 "leq"는 "<"와 같은 것을 상상해야합니다. –

관련 문제