0

다운로드 항목에 대해 여러 스레드에서 여러 종류의 처리를 수행하는 다운로더 응용 프로그램이 있습니다. 일부 스레드는 입력 데이터를 분석하고 일부는 다운로드, 추출, 상태 저장 등을 수행합니다. 따라서 스레드의 각 유형은 특정 데이터 구성원에서 작동하고 이러한 스레드 중 일부는 동시에 실행될 수 있습니다. 다운로드 항목은 다음과 같이 설명 할 수 있습니다.다중 스레드 처리 중에 데이터 저장

class File; 

class Download 
{ 
public: 
    enum State 
    { 
     Parsing, Downloading, Extracting, Repairing, Finished 
    }; 

    Download(const std::string &filePath): filePath(filePath) { } 

    void save() 
    { 
     // TODO: save data consistently 

     StateFile f; // state file for this download 

     // save general download parameters 
     f << filePath << state << bytesWritten << totalFiles << processedFiles; 

     // Now we are to save the parameters of the files which belong to this download, 
     // (!) but assume the downloading thread kicks in, downloads some data and 
     // changes the state of a file. That causes "bytesWritten", "processedFiles" 
     // and "state" to be different from what we have just saved. 

     // When we finally save the state of the files their parameters don't match 
     // the parameters of the download (state, bytesWritten, processedFiles). 
     for (File *f : files) 
     { 
      // save the file... 
     } 
    } 

private: 
    std::string filePath; 
    std::atomic<State> state = Parsing; 
    std::atomic<int> bytesWritten = 0; 
    int totalFiles = 0; 
    std::atomic<int> processedFiles = 0; 
    std::mutex fileMutex; 
    std::vector<File*> files; 
}; 

이 데이터를 일관되게 저장하는 방법을 궁금합니다. 예를 들어, 처리 된 파일의 상태와 수는 이미 저장되었을 수 있으며 파일 목록을 저장합니다. 한편 다른 스레드는 파일의 상태를 변경하여 결과 파일의 수나 다운로드 상태를 변경하여 저장된 데이터의 일관성을 유지할 수 있습니다.

첫 번째 생각은 모든 데이터 멤버에 단일 뮤텍스를 추가하고 중 어느 하나라도에 액세스 할 때마다 잠그는 것입니다. 그러나 대부분의 시간 스레드가 다른 데이터 멤버에 액세스하고 저장하는 데 몇 분 안에 단 한 번만 발생하므로 비효율적 일 수 있습니다.

다중 스레드 프로그래밍에서는 이러한 작업이 다소 일반적인 것처럼 보입니다. 경험있는 사람들이 더 나은 방법을 제안 할 수 있기를 바랍니다.

+0

* "마음에 오는 첫 번째 아이디어는 하나의 뮤텍스를 추가하는 것입니다 : 여기

어떻게 동시에 다운로드, 구문 분석, 추출 및 저장해야하는 경우 수행하는 샘플 큐에 대한 코드입니다 모든 데이터 멤버에 대해 액세스하고 그 중 하나에 액세스 할 때마다 잠그십시오. "* - 왜 여러 뮤텍스를 사용할 수없고 개인 회원에게 액세스를 잠글 수 있습니까? 그리고 클래스를 여러 개의 다른 클래스로 나눠서 각 스레드가 완료되고 부분 결과가 최종 결과로 모아 질 때까지 조용하게 자체 데이터 조각에서 작업 할 수 있도록하는 것이 어떻습니까? –

+0

글쎄, 위에서 설명한 것처럼 개별 멤버를 잠그더라도 전체 데이터 세트가 일관성없이 저장되는 것을 방지하지 못합니다. 예 : 저장된 다운로드 상태 및 처리 된 파일 수가 저장된 파일 목록과 일치하지 않을 수 있습니다. 글쎄, 스레드가 동일한 데이터 멤버를 사용할 수 있습니다. 나는 단지 그들이 모두를 사용할 수는 없다는 것을 의미했습니다. – mentalmushroom

답변

0

생산자 고객 패턴을 사용하는 것이 좋습니다.

다운로더는 파서를 생성하여 소비하도록 알리고, 파서는 추출기를 생성하고 소비 자에게 알리고 수리업자에게 추출기를 알립니다. 그러면 각 작업에 대한 대기열이 생깁니다. 조건 변수를 사용하여 동기화를 최적화 할 수 있으므로 소비자는 무언가가 생성되면 알림을받을 때만 소비자를 끌어 당깁니다. 매우 적은 뮤텍스와 훨씬 더 읽기 쉽고 효율적인 디자인을 사용하게 될 것입니다.

#include <thread> 
#include <condition_variable> 
#include <mutex> 
#include <queue> 
template<typename T> 
class synchronized_queu 
{ 
public: 
    T consume_one() 
    { 
     std::unique_lock<std::mutex> lock(lock_); 
     while (queue_.size() == 0) 
      condition_.wait(lock); //release and obtain again 
     T available_data = queue_.front(); 
     queue_.pop(); 
     return available_data; 
    } 
    void produce_one(const T& data) 
    { 
     std::unique_lock<std::mutex> lock(lock_); 
     queue_.push(data); 
     condition_.notify_one();//notify only one or all as per your design... 
    } 
private: 
    std::queue<T> queue_; 
    std::mutex lock_; 
    std::condition_variable condition_; 
}; 
struct data 
{ 
    //..... 
}; 

void download(synchronized_queu<data>& q) 
{ 
    //... 
    data data_downloaded = ; //data downloaded; 
    q.produce_one(data_downloaded); 
} 

void parse(synchronized_queu<data>& q1, synchronized_queu<data>& q2) 
{ 
    //... 
    data data_downloaded = q1.consume_one(); 
    //parse 
    data data_parsed = ;//.... 
    q2.produce_one(data_parsed); 
} 

void extract(synchronized_queu<data>& q1, synchronized_queu<data>& q2) 
{ 
    //... 
    data data_parsed = q1.consume_one(); 
    //parse 
    data data_extracted = ;//.... 
    q2.produce_one(data_extracted); 
} 
void save(synchronized_queu<data>& q) 
{ 
    data data_extracted = q.consume_one(); 
    //save.... 
} 

int main() 
{ 
    synchronized_queu<data> dowlowded_queue; 
    synchronized_queu<data> parsed_queue; 
    synchronized_queu<data> extracted_queue; 

    std::thread downloader(download, dowlowded_queue); 
    std::thread parser(parse, dowlowded_queue, parsed_queue); 
    std::thread extractor(extract, parsed_queue, extracted_queue); 
    std::thread saver(save, extracted_queue); 
    while (/*condition to stop program*/) 
    { 

    } 
    downloader.join(); 
    parser.join(); 
    extractor.join(); 
    saver.join(); 
    return 0; 
} 
+0

이러한 작업은 동시에 실행되도록되어 있으므로 대기열이 없습니다. 저축은 다른 스레드가하는 일과 관계없이 주기적으로 발생해야합니다. – mentalmushroom

+0

예, 그게 제가 제안하고 싶었던 것입니다. 간단한 설명을 위해 다운로더와 추출기 만 갖도록하겠습니다. 당신은 동시에 두 개의 스레드 다운로더와 추출기가 같은 큐 Q1을 공유하기 시작합니다. 다운로더는 큐에 넣은 것을 다운로드하고 추출기가 대기열에서 빠져 나오면 처리합니다 .... –

+0

다른 스레드에서 처리하는 동안 제 질문은 데이터를 저장하는 것이 었습니다. – mentalmushroom