#c_sharp
Не совсем понимаю работу многопоточности, в моем представлении многопоточность выглядит так. Это человек который роет яму (один поток) и к нему ты добавляешь еще одного и типа они должны рыть два раза быстрее. Но тут встает вопрос о разделении площади работы на двоих, чтобы они друг-другу не мешали? Так ли это? Чтобы перейти к более практичным вещам, я разрабатываю парсер, который работает по такой схеме. У меня есть xml файл с ссылками на страницу товара, я читаю этот xml и затем сразу же перехожу по ссылке загружаю не достающую информацию по товару в базу. Скорость работы меня не устраивает и я решил разобраться в многопоточности. Обдумываю такую схему, я читаю весь xml и записываю его в базу, после того как я его полностью записал в базу, начинаю создавать потоки и каждому из них передавать записи, одному например четную запись из базы, другому не четную. По идеи скорость должна возрасти в два раза и не возникнет ли у меня проблем если эти два потока решат одновременно произвести операцию записи в базу. И вообще является ли мой алгоритм правильным? Будет приятно, если вы напишите на образных примерах и немного кода тоже не помешает.
Ответы
Ответ 1
Дело в том, что нет смысла читать файл(ы) с одного физического накопителя в несколько потоков - это абсолютно не увеличит производительность (а может даже уменьшит). Так что, в вашем случае, скорее всего, узким местом будет именно чтение 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. Вероятно, их использование будет эффективней, чем ручное написание многопоточного кода. Ответ 2
Парсить xml многопоточно - это сомнительная затея. Зачем-то использовать базу - на мой взгляд, тоже. По крайней мере, в вопросе я не увидел ничего, для чего была бы нужна БД. А вот посылать запросы за недостающей информацией в отдельном потоке было бы вполне логично. Один поток парсит xml, и запрашивает недостающую информацию через другой. Также стоит обдумать, сколько одновременных соединений можно использовать. Думаю, для этого подойдёт Parallel.For или можно самому написать что-нибудь подобное. PS: Стоит обратить внимание на эту статью.
Комментариев нет:
Отправить комментарий