Многопоточность вашего кода
Давайте начнем с написания многопоточной 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;
}