Открытие нескольких потоков с объектом и возвращение результата - PullRequest
1 голос
/ 09 апреля 2019

Я пытаюсь открыть несколько потоков через цикл, где каждый поток является экземпляром класса, чей конструктор перегружен таким образом, что он автоматически выполняет нужный код, эта функция возвращает unordered_list, и я хотел бы получить его для этогоконкретный экземпляр для последующего добавления к окончательному списку unordered_list

Я пытался использовать фьючерсы и обещания, но в итоге я запутался, пытаясь это сделать.Этот проект призван бросить мне вызов и помочь мне научиться многопоточности на c ++.

    //class to be instantiated per thread   
    class WordCounter {
    public:
        std::unordered_map<std::string, int> thisWordCount;
        std::string word;

        WordCounter(std::string filepath) {}//will be overloaded
        ~WordCounter() {}//destructor

        std::unordered_map<std::string, int>operator()(std::string filepath) const {}//overloaded constructor signature
        std::unordered_map<std::string, int>operator()(std::string currentFile) {//overloaded constructor implementation
            fstream myReadFile;
            myReadFile.open(currentFile);
            if (!!!myReadFile) {
                cout << "Unable to open file";
                exit(1); // terminate with error
            }
            else if (myReadFile.is_open()) {
                while (!myReadFile.eof()) {
                    while (myReadFile >> word) {
                        ++thisWordCount[word];
                    }
                }
            }
            myReadFile.close();

            return thisWordCount;
        }
    };


    int main(int argc, char** argv)
    {
        std::vector<std::thread> threads;//store instantiated threads using WordCounter
        static std::unordered_map<std::string, int> finalWordCount; //append result from each thread to this unordered_list only when a particular thread finish's reading a file
        vector<string> fileName = { "input1.txt" , "input2.txt" };//filepaths to the files used

        for (int i = 0; i < fileName.size(); ++i)//loop through vector of filepaths to open a thread for each file to then be processed by that thread
        {
            std::string currentFile = DIR + fileName[i];
            std::thread _newThread(new WordCount(currentFile); //is this how the thread would be created?
            threads.emplace_back(_newThread);//store new thread in a vector

//I want to read through the vector when a particular thread finishes and append that particular threads result to finalWordCount

        }

}

1 Ответ

1 голос
/ 09 апреля 2019

Многопоточность вашего кода

Давайте начнем с написания многопоточной countWords функции. Это даст нам общий обзор того, что должен делать код, а затем мы восполним недостающие части.

Письмо countWords

countWords считает частоты слов в каждом файле в векторе имен файлов. Делает это параллельно.

Обзор шагов:

  • Создать вектор из потоков
  • Укажите место для хранения конечного результата (это переменная finalWordCount)
  • Сделать функцию обратного вызова для WordCounter, чтобы вызывать, когда это будет сделано
  • Создать новый поток для каждого файла с WordCounter объектом.
  • Дождаться окончания жаб
  • возврат finalWordCount

A WordCounter объект принимает имя файла в качестве ввода при запуске потока.

недостающие части:

  • Нам все еще нужно написать makeWordCounter функцию

реализация внешний:

using std::unordered_map;
using std::string; 
using std::vector; 

unordered_map<string, int> countWords(vector<string> const& filenames) {
    // Create vector of threads
    vector<std::thread> threads;
    threads.reserve(filenames.size());

    // We have to have a lock because maps aren't thread safe
    std::mutex map_lock;

    // The final result goes here
    unordered_map<std::string, int> totalWordCount; 

    // Define the callback function
    // This operation is basically free
    // Internally, it just copies a reference to the mutex and a reference
    // to the totalWordCount
    auto callback = [&](unordered_map<string, int> const& partial_count) {
        // Lock the mutex so only we have access to the map
        map_lock.lock(); 
        // Update the map
        for(auto count : partial_count) {
            totalWordCount[count.first] += count.second; 
        }
        // Unlock the mutex
        map_lock.unlock(); 
    };

    // Create a new thread for each file
    for(auto& file : filenames) {
        auto word_counter = makeWordCounter(callback); 
        threads.push_back(std::thread(word_counter, file)); 
    }

    // Wait until all threads have finished
    for(auto& thread : threads) {
        thread.join(); 
    }

    return totalWordCount; 
}

Письмо makeWordCounter

Наша функция makeWordCounter очень проста: она просто создает функцию WordCounter, которая настроена на обратный вызов.

template<class Callback>
WordCounter<Callback> makeWordCounter(Callback const& func) {
    return WordCounter<Callback>{func}; 
}

Запись WordCounter класса

Переменные-члены:

  • Функция обратного вызова (нам больше ничего не нужно)

Функции

  • operator() звонит countWordsFromFilename с именем файла
  • countWordsFromFilename открывает файл, проверяет, все ли в порядке, и вызывает countWords с потоком файлов
  • countWords читает все слова в файловом потоке и вычисляет счетчик, затем вызывает обратный вызов с окончательным счетчиком.

Поскольку WordCounter действительно просто, я просто сделал его структурой. Для этого нужно только сохранить функцию Callback, и, сделав функцию callback общедоступной, нам не нужно писать конструктор (компилятор обрабатывает его автоматически с помощью агрегатной инициализации).

template<class Callback>
struct WordCounter {
    Callback callback;

    void operator()(std::string filename) {
        countWordsFromFilename(filename); 
    }
    void countWordsFromFilename(std::string const& filename) {
        std::ifstream myFile(filename);
        if (myFile) {
            countWords(myFile); 
        }
        else {
            std::cerr << "Unable to open " + filename << '\n'; 
        }
    }
    void countWords(std::ifstream& filestream) {
        std::unordered_map<std::string, int> wordCount; 
        std::string word; 
        while (!filestream.eof() && !filestream.fail()) {
            filestream >> word; 
            wordCount[word] += 1;
        }
        callback(wordCount); 
    }
};

Полный код

Вы можете увидеть полный код для countWords здесь: https://pastebin.com/WjFTkNYF

Единственное, что я добавил, это #include s.

Обратные вызовы и шаблоны 101 (по запросу Original Poster)

Шаблоны - это простой и полезный инструмент для написания кода. Они могут быть использованы для устранения взаимозависимостей; сделать алгоритмы общими (чтобы их можно было использовать с любыми типами); и они могут даже сделать код быстрее и эффективнее, позволяя вам избегать вызовов виртуальных функций-членов или указателей на функции.

Шаблонный класс

Давайте рассмотрим действительно простой шаблон класса, представляющий пару:

template<class First, class Second>
struct pair {
    First first;
    Second second; 
};

Здесь мы объявили pair как struct, потому что мы хотим, чтобы все участники были публичными.

Обратите внимание, что нет типа First и типа Second. Когда мы используем имена First и Second, то, что мы на самом деле говорим, это «в контексте класс pair, имя First будет представлять аргумент First класса пары, а имя Second будет представлять второй элемент класса пары.

Мы могли бы просто написать это как:

// This is completely valid too
template<class A, class B>
struct pair {
    A first;
    B second; 
};

Использование pair довольно просто:

int main() {
    // Create pair with an int and a string
    pair<int, std::string> myPair{14, "Hello, world!"}; 

    // Print out the first value, which is 14
    std::cout << "int value:    " << myPair.first << '\n';
    // Print out the second value, which is "Hello, world!"
    std::cout << "string value: " << myPair.second << '\n';
}

Как и обычный класс, pair может иметь функции-члены, конструктор, деструктор ... что угодно. Поскольку pair такой простой класс, компилятор автоматически генерирует для нас конструктор и деструктор, и нам не нужно о них беспокоиться.

Шаблонные функции

Шаблонные функции похожи на обычные функции. Единственное отличие состоит в том, что они имеют объявление template перед остальной частью объявления функции.

Давайте напишем простую функцию для печати пары:

template<class A, class B>
std::ostream& operator<<(std::ostream& stream, pair<A, B> pair) 
{
    stream << '(' << pair.first << ", " << pair.second << ')'; 
    return stream; 
}

Мы можем дать ему любой pair, какой захотим, при условии, что он умеет печатать оба элемента пары:

int main() {
    // Create pair with an int and a string
    pair<int, std::string> myPair{14, "Hello, world!"}; 

    std::cout << myPair << '\n'; 
}

Это выводит (14, Hello, world).

Callbacks

В C ++ нет типа Callback. Нам это не нужно. Обратный вызов - это просто то, что вы используете, чтобы указать, что что-то произошло.

Давайте посмотрим на простой пример.Эта функция ищет постепенно увеличивающиеся числа, и каждый раз, когда она находит их, она вызывает output, что является предоставленным нами параметром.В этом случае output является обратным вызовом, и мы используем его, чтобы указать, что был найден новый большой номер.

template<class Func>
void getIncreasingNumbers(std::vector<double> const& nums, Func output) 
{
    // Exit if there are no numbers
    if(nums.size() == 0) 
        return; 

    double biggest = nums[0]; 
    // We always output the first one
    output(biggest); 
    for(double num : nums) 
    {
        if(num > biggest) 
        {
            biggest = num; 
            output(num); 
        }
    }
}

Мы можем использовать getIncreasingNumbers разными способами.Например, мы можем отфильтровать числа, которые были не больше предыдущего:

std::vector<double> filterNonIncreasing(std::vector<double> const& nums) 
{
    std::vector<double> newNums; 
    // Here, we use an & inside the square brackets
    // This is so we can use newNums by reference
    auto my_callback = [&](double val) { 
        newNums.push_back(val); 
    };
    getIncreasingNumbers(nums, my_callback); 
    return newNums; 
}

Или мы можем распечатать их:

void printNonIncreasing(std::vector<double> const& nums) 
{
    // Here, we don't put anything in the square brackts
    // Since we don't access any local variables
    auto my_callback = [](double val) {
        std::cout << "New biggest number: " << val << '\n'; 
    };
    getIncreasingNums(nums, my_callback); 
}

Или мы можем найти самый большой разрывмежду ними:

double findBiggestJumpBetweenIncreasing(std::vector<double> const& nums)
{
    double previous; 
    double biggest_gap = 0.0; 
    bool assigned_previous = false;
    auto my_callback = [&](double val) {
        if(not assigned_previous) {
            previous = val; 
            assigned_previous = true;
        }
        else 
        {
            double new_gap = val - previous; 
            if(biggest_gap < new_gap) {
                biggest_gap = new_gap; 
            }
        }
    };
    getIncreasingNums(nums, my_callback); 
    return biggest_gap;
}
...