저는 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 년이나 무언가와 같았습니다.
코드가 너무 많습니다./코드가 충분하지 않습니다. 여전히 문제가있는 최소한의 샘플로 줄이면 처리 과정에서 오류를 찾을 수 있습니다. –
helgrind가 식별 한 모든 종족을 고친 다음 다시 시도하십시오. – caf
당신이 stdio ('std :: cout')에 동시에 접근하려고 시도하고 있기 때문에 현재 테스트 프로그램에서 볼 수있는 대부분의 경주가 있습니다. – caf