#c# #multithreading #producer-consumer
#c# #многопоточность #производитель-потребитель
Вопрос:
Я хотел бы сначала выполнить поиск по каталогу, а затем по файлам внутри него, по ключевому слову.
Я знаю, что мне нужны два класса, класс производителя и класс потребителя, но я не знаю, как выполнить поиск с использованием очереди производителя / потребителя c #?
public class Program
{
private static void Main()
{
Queue<File> searchFile = new Queue<File>();
Queue<Directory> searchDirectory = new Queue<Directory>();
new Thread(searchDirectory).Start();
for (int i = 0; i < 3; i )
new Thread(searchFile).Start();
}
}
Комментарии:
1. Это действительно отличный вопрос.
Ответ №1:
Начальные проблемы:
- Вы объявляете 2 переменные разных типов, используя одно и то же имя переменной с одинаковой областью видимости.
- Вы не хотите запускать потоковый поиск в каталоге, а другой — в файле.
Проблема с пунктом № 2 заключается в том, что вы работаете с одним из самых больших узких мест с несколькими потоками — то есть с дисковым вводом-выводом. Вы ничего не получите, выполняя дисковый ввод-вывод (на стандартном жестком диске), реализуя более 1 рабочего потока.
Объясните подробнее, что вы пытаетесь сделать (с примером, пожалуйста). Может быть лучший процесс.
Ответ №2:
Во-первых, Directory
это статический класс, поэтому вы не сможете использовать его в коллекции. Вместо этого вам нужно будет использовать DirectoryInfo
. Во-вторых, я бы использовал единую очередь, которая будет содержать DirectoryInfo
экземпляры. Затем файлы могут быть перечислены как часть обработки одной папки.
Вот как я бы это сделал, используя шаблон производитель-потребитель. Эта реализация использует BlockingCollection
класс which в качестве реализации блокирующей очереди. Блокирующие очереди весьма полезны в шаблоне производитель-потребитель, потому что они абстрагируют почти все сведения о производителе-потребителе.
public class Searcher
{
private BlockingCollection<DirectoryInfo> m_Queue = new BlockingCollection<DirectoryInfo>();
public Searcher()
{
for (int i = 0; i < NUMBER_OF_THREADS; i )
{
var thread = new Thread(Run);
thread.IsBackground = true;
thread.Start();
}
}
public void Search(DirectoryInfo root)
{
m_Queue.Add(root);
}
private void Run()
{
while (true)
{
// Wait for an item to appear in the queue.
DirectoryInfo root = m_Queue.Take();
// Add each child directory to the queue. This is the recursive part.
foreach (DirectoryInfo child in root.GetDirectories())
{
m_Queue.Add(child);
}
// Now we can enumerate each file in the directory.
foreach (FileInfo child in root.GetFiles())
{
// Add your search logic here.
}
}
}
}
Я должен отметить, что большинство дисков работают более сериализованным образом, поэтому наличие нескольких потоков, пытающихся выполнить поиск по файлам, может не принести вам много пользы, если только часть вашей логики, привязанная к процессору, не является обширной.
Ответ №3:
Как предполагают другие постеры, несколько потоков, пытающихся выполнить ввод-вывод, вызовут проблемы. Однако их можно было бы использовать для создания полной очереди директорий (если она была очень глубокой), а затем отдельного потока для выполнения регулярных выражений в файле. Примерно так:
class Program
{
static void Main(string[] args)
{
ConcurrentQueue<DirectoryInfo> concurrentQueue = new ConcurrentQueue<DirectoryInfo>();
GetAllDirectories(new DirectoryInfo(@"C:localoracle"), concurrentQueue);
Action action = () =>{
const string toFind = "ora";
DirectoryInfo info;
while(concurrentQueue.TryDequeue(out info))
{
FindInFile(toFind, info);
}
};
Parallel.Invoke(action, action, action, action);
Console.WriteLine("total found " _counter);
Console.ReadKey();
}
static int _counter = 0;
static void FindInFile(string textToFind,DirectoryInfo dirInfo)
{
var files =dirInfo.GetFiles();
foreach(FileInfo file in files)
{
using (StreamReader reader = new StreamReader(file.FullName))
{
string content = reader.ReadToEnd();
Match match = Regex.Match(content, textToFind, RegexOptions.Multiline);
if(match.Success)
{
Interlocked.Increment(ref _counter);
Console.WriteLine(file.FullName " found " match.Captures.Count);
foreach(var t in match.Captures)
{
Console.WriteLine("-------------> char index" match.Index);
}
}
}
}
}
internal static void GetAllDirectories(DirectoryInfo root, ConcurrentQueue<DirectoryInfo> values)
{
foreach (var di in root.GetDirectories())
{
GetAllDirectories(di, values);
values.Enqueue(di);
}
}
}
Ответ №4:
Я отредактировал сообщение (которое ожидает рецензирования). Если это будет одобрено, я отредактировал код, чтобы исправить основную проблему области видимости и опечаток, но я не думаю, что вы готовы к многопоточности, не говоря уже об очередях производителя-потребителя (Бог свидетель, я какое-то время баловался многопоточностью, и в конечном итоге я все еще путаю свои реализации, но это, вероятно, только я!).
Сначала вы должны освоиться с областями видимости и многопоточностью. Особенно прочитайте о проблемах механизма блокировки / параллелизма, которые имеют решающее значение для реализации успешного многопоточного решения.
Во-вторых, как предлагает IAbstract, действительно реализуйте несколько потоков с помощью мьютексов / семафоров, чтобы повысить производительность при многопоточности, а также получить желаемую очередь производителя-потребителя.
Также, если вам удобно, вы также можете посмотреть последнюю версию библиотеки потоков данных Async CTP1, которая имеет последнюю поддержку этого шаблона с использованием библиотеки Tasks Parallel. В качестве альтернативы вы можете использовать BlockingCollection
для реализации этого шаблона.
У Stackoverflow также есть вопросы, связанные с вашим вопросом, с некоторыми отличными ответами. Просто найдите «производитель-потребитель», чтобы прочитать их