Страницы

Поиск по вопросам

вторник, 9 апреля 2019 г.

Как мне работать с многопоточностью при парсинге?

Не совсем понимаю работу многопоточности, в моем представлении многопоточность выглядит так.
Это человек который роет яму (один поток) и к нему ты добавляешь еще одного и типа они должны рыть два раза быстрее. Но тут встает вопрос о разделении площади работы на двоих, чтобы они друг-другу не мешали? Так ли это?
Чтобы перейти к более практичным вещам, я разрабатываю парсер, который работает по такой схеме.
У меня есть xml файл с ссылками на страницу товара, я читаю этот xml и затем сразу же перехожу по ссылке загружаю не достающую информацию по товару в базу.
Скорость работы меня не устраивает и я решил разобраться в многопоточности.
Обдумываю такую схему, я читаю весь xml и записываю его в базу, после того как я его полностью записал в базу, начинаю создавать потоки и каждому из них передавать записи, одному например четную запись из базы, другому не четную.
По идеи скорость должна возрасти в два раза и не возникнет ли у меня проблем если эти два потока решат одновременно произвести операцию записи в базу. И вообще является ли мой алгоритм правильным?
Будет приятно, если вы напишите на образных примерах и немного кода тоже не помешает.


Ответ

Дело в том, что нет смысла читать файл(ы) с одного физического накопителя в несколько потоков - это абсолютно не увеличит производительность (а может даже уменьшит). Так что, в вашем случае, скорее всего, узким местом будет именно чтение xml.
А вот дальнейшую обработку, уже после чтения с диска, вполне можно выполнять в отдельных потоках. Для этого удобно применять конвейеры (pipelines).
Например, шаблон может выглядеть следующим образом:
var inputValues = new BlockingCollection();
var readXml = Task.Run(() => { try { using (var xmlReader = XmlReader.Create("test.xml")) { while (xmlReader.ReadToFollowing("nodeName")) inputValues.Add(xmlReader.ReadElementContentAsString()); } } finally { inputValues.CompleteAdding(); } });
var processValues = Task.Run(() => { foreach (var value in inputValues.GetConsumingEnumerable()) { // обрабатываем value } });
Task.WaitAll(readXml, processValues);
Такой способ также называют производитель-потребитель (producer-consumer). Один поток производит данные - другой(ие) их потребляет.
Использование BlockingCollection автоматически обеспечивает много преимуществ, о которых можно почитать в справке.
При необходимости, количество стадий конвейера можно легко увеличивать:
var inputValues = new BlockingCollection(); var processedValues = new BlockingCollection();
var readXml = Task.Run(() => { try { using (var xmlReader = XmlReader.Create("test.xml")) { while (xmlReader.ReadToFollowing("nodeName")) inputValues.Add(xmlReader.ReadElementContentAsString()); } } finally { inputValues.CompleteAdding(); } });
var processValues = Task.Run(() => { try { foreach (var value in inputValues.GetConsumingEnumerable()) { // обрабатываем value var newValue = Process(value); processedValues.Add(newValue); } } finally { processedValues.CompleteAdding(); } });
var writeToDB = Task.Run(() => { foreach (var value in processedValues.GetConsumingEnumerable()) { // записываем данные в БД } });
Task.WaitAll(readXml, processValues, writeToDB);
Если один из потоков работает намного быстрее других и забивает память, то можно ограничить ёмкость его коллекции:
var inputValues = new BlockingCollection(boundedCapacity: 50);
Таким образом, заполнив коллекцию до указанного значения, он будет ждать, пока другие потоки не выберут данные.
Обработку данных внутри каждой стадии конвейера можно дополнительно распараллелить, если это необходимо и вообще возможно:
foreach (var value in inputValues.GetConsumingEnumerable() .AsParallel() // распараллеливаем обработку .AsOrdered() // обеспечиваем правильный порядок, если нужно ) { // обрабатываем value }

Можно ли в несколько потоков записывать данные в БД? Вероятно, на эту тему нужно задать отдельный вопрос, обязательно указав, какая именно СУБД используется и прочую информацию.
Многие СУБД имеют специальные возможности по массовому эскпорту и импорту данных. Например, SQL Server. Вероятно, их использование будет эффективней, чем ручное написание многопоточного кода.

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

Отправить комментарий