16 байт (назовите это 16B) слишком мало для эффективной связи между потоками. Постановка в очередь таких небольших буферов приведет к увеличению затрат ЦП на связь между потоками, чем на фактическую полезную обработку данных.
Итак, разбейте их на куски.
Объявите некоторый буферный класс (скажем, C16B), который содержит хороший, большой массив этих 16B - по крайней мере, 4 КБ, и значение 'count' int, чтобы показать, сколько загружено (последний буфер, загруженный из файл, вероятно, не будет полным). Это поможет, если вы поместите пустой байтовый массив размером с строку кэша прямо перед этим массивом 16B - это поможет избежать ложного разделения. Возможно, вы можете поместить код, который обрабатывает 16B, как метод, Process16B, sya и, возможно, код, который загружает массив тоже - принимает дескриптор файла в качестве параметра. Этот класс теперь может быть эффективно загружен в очередь в другие потоки.
Вам нужен класс очереди производитель-потребитель - в C # он уже есть в классах BlockingCollection.
Вам нужно управление потоком в этом приложении. Я бы сделал это, создав пул C16B - создайте очередь блокировки и создайте / добавьте большую кучу C16B в цикле. 1024 - хорошее круглое число. Теперь у вас есть «очередь пула», которая обеспечивает управление потоком, избавляет от необходимости использовать new () любых C16B, и вам не нужно, чтобы они постоянно собирались мусором.
Если у вас есть это, все остальное легко. В потоке загрузчика постоянно исключайте C16B из очереди пула, загружайте их данными из файлов и добавляйте () их в потоки обработки в очереди блокировки '16Bprocess'. В потоках обработки возьмите () из очереди 16Bprocess и обработайте каждый экземпляр C16B, вызвав его метод Process16B. После обработки 16B добавьте () C16B обратно в очередь пула для повторного использования.
Переработка C16B через очередь пула обеспечивает сквозное управление потоком. Если производитель является самой быстрой ссылкой, пул в конечном итоге будет пустым, и производитель будет блокировать его до тех пор, пока потребитель не вернет немного C16B.
Если обработка занимает так много времени, вы всегда можете добавить другой поток обработки, если у вас есть запасные ядра. Проблема с такими схемами в том, что данные будут обрабатываться не по порядку. Это может иметь или не иметь значение. Если это произойдет, поток данных может потребоваться «уладить» позже, например. используя порядковые номера и список буферов.
Я бы посоветовал сбросить счетчик очереди пула (и, возможно, счетчик очереди 16B) в компонент состояния или командную строку с таймером. Это обеспечивает полезный снимок того, где находятся экземпляры C16B, и вы можете увидеть узкие места и любые утечки C16B без сторонних инструментов (те, которые замедляют работу всего приложения до обхода и выдают ложные отчеты об утечках при завершении работы).