Суть проблемы в том, чтобы одновременно пройтись по двум спискам:
- список ввода, представляющий собой список файлов
- список вывода, представляющий собой список потоков (где каждый поток имеет список диапазонов)
Я считаю, что самый простой подход к таким проблемам - это бесконечный цикл, который выглядит примерно так:
while (1)
{
move some information from the input to the output
decide whether to advance to the next input item
decide whether to advance to the next output item
if we've reached (the end of the input _OR_ the end of the output)
break
if we advanced to the next input item
prepare the next input item for processing
if we advanced to the next output item
prepare the next output item for processing
}
Чтобы отслеживать ввод, нам нужна следующая информация
fileIndex
указатель в список файлов
fileOffset
смещение первого неназначенного байта в файле, изначально 0
fileRemain
количество байтов в файле, которые не были назначены, изначально размер файла
Чтобы отслеживать вывод, нам нужно
threadIndex
индекс потока, над которым мы сейчас работаем (который является первым индексом в List<List<FileRange>>
, который генерирует алгоритм)
threadNeeds
количество байтов, которое еще нужно потоку, изначально base
или base+1
Примечание: я использую base
в качестве минимального числа байтов, назначенных каждому потоку (sum/n
), и extra
в качестве количества потоков, которые получают дополнительный байт (sum%n
).
Итак, теперь мы добрались до сути алгоритма: какую информацию перемещать от входа к выходу:
- если
fileRemain
меньше threadNeeds
, тогда остальная часть файла (который может быть всем файлом) будет присвоена текущей нити, и мы перейдем к следующему файлу
- если
fileRemain
больше threadNeeds
, то часть файла присваивается текущей нити, и мы переходим к следующей нити
- если
fileRemain
равно threadNeeds
, то остальная часть файла назначается потоку, и мы переходим к следующему файлу и следующему потоку
Эти три случая легко обрабатываются путем сравнения fileRemain
и threadNeeds
и выбора byteCount
, который является минимумом из двух.
Имея все это в виду, вот несколько псевдокодов, которые помогут вам начать:
base = sum/n;
extra = sum%n;
// initialize the input control variables
fileIndex = 0
fileOffset = 0
fileRemain = length of file 0
// initialize the output control variables
threadIndex = 0
threadNeeds = base
if (threadIndex < extra)
threadNeeds++
while (1)
{
// decide how many bytes can be assigned, and generate some output
byteCount = min(fileRemain, threadNeeds)
add (file.name, fileOffset, fileOffset+byteCount-1) to the list of ranges
// decide whether to advance to the next input and output items
threadNeeds -= byteCount
fileRemain -= byteCount
if (threadNeeds == 0)
threadIndex++
if (fileRemain == 0)
fileIndex++
// are we done yet?
if (threadIndex == n || fileIndex == files.size())
break
// if we've moved to the next input item, reinitialize the input control variables
if (fileRemain == 0)
{
fileOffset = 0
fileRemain = length of file
}
// if we've moved to the next output item, reinitialize the output control variables
if (threadNeeds == 0)
{
threadNeeds = base
if (threadIndex < extra)
threadNeeds++
}
}
Подсказка по отладке: достижение конца ввода и конца вывода должно происходить одновременно. Другими словами, у вас должны заканчиваться файлы в то же время, когда у вас заканчиваются потоки. Поэтому во время разработки я проверял бы оба условия и проверял, что они действительно изменяются одновременно.