Многопоточная итерация в C

#c #multithreading

#c #Многопоточность

Вопрос:

Я новичок в c , и я пытаюсь повторить результат equal_range(key) применения к multimap, который является итератором. Прямо сейчас я могу выполнить итерацию по результату, используя цикл for result.first и result.second значения and, как в этом примере:

 #include <iostream>
#include <map>

int main ()
{
  std::multimap<char,int> mymm;

  mymm.insert(std::pair<char,int>('a',10));
  mymm.insert(std::pair<char,int>('b',20));
  mymm.insert(std::pair<char,int>('b',30));
  mymm.insert(std::pair<char,int>('b',40));
  mymm.insert(std::pair<char,int>('c',50));
  mymm.insert(std::pair<char,int>('c',60));
  mymm.insert(std::pair<char,int>('d',60));


  auto ret = mymm.equal_range('b');
  for (auto it=ret.first; it!=ret.second;   it)
      std::cout << ' ' << it->second;

} 
 

В моем реальном случае мне нужно выполнять итерации быстрее (более тысячи строк за короткий промежуток времени), поэтому я пытаюсь выполнять итерации с использованием нескольких потоков. Однако я не могу этого сделать, потому что моя идея заключалась в том, чтобы разделить итератор на разные части, а затем выполнить итерацию по каждой части в разных потоках. Но это невозможно, потому что я не могу сделать что-то вроде it = it 10 запуска итерации по разным позициям.

Поэтому, по сути, мне нужно разделить диапазон итераторов неслучайного доступа на несколько меньших диапазонов. Есть ли какой-нибудь безопасный способ сделать это?

Комментарии:

1. Почему вам нужно выполнять итерации быстрее? Сколько совпадающих ключей вы ожидаете? В этом небольшом примере стоимость развертывания новых потоков значительно перевесит любые потенциальные выгоды.

2. Я думаю, что лучший способ задать вопрос — «Разделить диапазон итераторов неслучайного доступа на несколько меньших диапазонов» или что-то в этом роде. Вопрос по своей сути не связан с многопоточностью.

3. @0x5453 это всего лишь пример. Мне нужно перебрать тысячи ключей. Я обновил вопрос.

4. Помните, что если у вас есть несколько меньших диапазонов, вам все равно нужно быть осторожным при синхронизации результатов ваших отдельных потоков.

Ответ №1:

Как было указано в комментарии, ваш вопрос сводится к тому, «Как разделить диапазон итераторов неслучайного доступа на несколько меньших диапазонов». Как только вы это получите, вы можете передать эти меньшие диапазоны нескольким потокам.

Вы не можете it 10 , когда it не является итератором произвольного доступа. Однако вы можете использовать std::advance для продвижения итератора.

Чтобы проиллюстрировать это:

 #include <iostream>
#include <map>
#include <vector>

template <typename IT>
std::vector<IT> split_range(IT begin, IT end,size_t chunksize){
    std::vector<IT> resu<
    auto it = begin;
    result.push_back(it);
    size_t total = 0;
    size_t size = std::distance(begin,end);
    while (total   chunksize < size){
        std::advance(it,chunksize);
        result.push_back(it);
        total  = chunksize;
    }
    if (it != end) result.push_back(end);
    return resu< 
}



int main ()
{
  std::multimap<char,int> mymm;

  mymm.insert(std::pair<char,int>('a',10));
  mymm.insert(std::pair<char,int>('b',20));
  mymm.insert(std::pair<char,int>('b',30));
  mymm.insert(std::pair<char,int>('b',40));
  mymm.insert(std::pair<char,int>('c',50));
  mymm.insert(std::pair<char,int>('c',60));
  mymm.insert(std::pair<char,int>('d',60));


  auto ret = mymm.equal_range('b');
  
  auto splitted = split_range(ret.first,ret.second,1);
  for (auto it = splitted.begin(); std::next(it) != splitted.end();   it){
       std::cout << "printing one chunkn";
       for (auto it2 = *it; it2 != *std::next(it);   it2){
           std::cout << ' ' << it2->second << "n";
       }
  }
}
 

split_range вероятно, не самая эффективная. std::advance имеет свою цену, вам нужно выполнить итерацию всего диапазона один раз в одном потоке. А также std::distance не является бесплатной для итераторов с неслучайным доступом (в основном требуется повторение всего диапазона во второй раз). Хотя, как только вы разделили диапазон, вы можете использовать несколько потоков для параллельной обработки поддиапазонов.

Комментарии:

1. Важно отметить, что одному потоку все равно придется выполнять итерации по всему диапазону, поскольку std::advance он имеет линейную сложность. Просто работа, которую должен выполнять поток, предположительно намного меньше, чем то, что на самом деле должно быть сделано для каждого элемента.

2. @FrancoisAndrieux юп, только что добавил примечание. Я предположил, что OP знает о накладных расходах, и рабочая нагрузка достаточна для оправдания начальных накладных расходов

Ответ №2:

Один из способов распараллелить работу — использовать <execution> политики, добавленные в C 17.

Используется с std::for_each ним может выглядеть следующим образом:

 #include <algorithm>
#include <execution>
#include <iostream>
#include <map>

int main() {
    std::multimap<char, int> mymm;

    mymm.insert(std::pair<char, int>('a', 10));
    mymm.insert(std::pair<char, int>('b', 20));
    mymm.insert(std::pair<char, int>('b', 30));
    mymm.insert(std::pair<char, int>('b', 40));
    mymm.insert(std::pair<char, int>('c', 50));
    mymm.insert(std::pair<char, int>('c', 60));
    mymm.insert(std::pair<char, int>('d', 60));

    auto [begin, end] = mymm.equal_range('b');

    std::for_each(std::execution::par, begin, end,
        [](const autoamp; p)
        {
            std::cout << ' ' << p.second;
        }
    );
}