Как прочитать файл с json-подобными объектами в C с многопоточностью

Я пытаюсь прочитать файл для многопоточного чтения файла на C, но поскольку я разделяю его на куски в зависимости от размера файла, некоторые из них могут начинаться/кончаться в середине строк. Я пытался отрегулировать размер фрагмента на случай, если это произойдет. Линии не имеют одинакового размера. Я пытаюсь прочитать очень большой файл размером около 100 МБ.

Прежде всего, хороший ли это подход или многопоточность следует использовать только для других задач, таких как, например, обработка строки?

Мой подход заключался в том, чтобы сдвинуть начало и конец фрагмента, пока он не найдет конец строки. Таким образом, если фрагмент окажется внутри строки (когда n. потоков > n. строк), начало и конец будут совпадать, и я не буду запускать этот поток. Большую часть времени он читается нормально, но иногда одна строка не читается, или строка читается дважды одним и тем же потоком, или небольшая часть строки читается другим потоком.

Есть ли проблема, когда разные потоки одновременно читают один и тот же файл?

long chunk_size = file_size / num_threads;

pthread_t threads[num_threads];
ThreadData thread_data[num_threads];

long last_end = 0;

for (uint32_t i = 0; i < num_threads; ++i)
{
    thread_data[i].stats = stats;
    thread_data[i].thread_tweets = NULL;
    thread_data[i].failed = 0;

    thread_data[i].file = file;
    thread_data[i].start = i * chunk_size;
    thread_data[i].end = (i == num_threads - 1) ? file_size : (i + 1) * chunk_size;
    
    if (i > 0)
    {
        if (thread_data[i].end < thread_data[i - 1].start)
        {
            thread_data[i].failed = 1;
            continue;
        }
    }
    int ch;
    // Adjust start position to the beginning of the next line
    if (!is_start_at_line_boundary(file, thread_data[i].start))
    {
        fseek(file, thread_data[i].start, SEEK_SET);
        while ((ch = fgetc(file)) != '\n' && ch != EOF);
        thread_data[i].start = ftell(file);
    }

    // Adjust end position to the end of the line
    fseek(file, thread_data[i].end, SEEK_SET);
    while ((ch = fgetc(file)) != '\n' && ch != EOF);
    thread_data[i].end = ftell(file);
    if (ch != '\n' && ch != EOF)
    {
        thread_data[i].end++;
    }
    // If they coincide, the chunk was inside a line and the thread shoudnt run
    if (thread_data[i].end == thread_data[i].start)
    {
        thread_data[i].failed = 1;
        continue;
    }
    if (i > 0)
    { 
        thread_data[i].start = last_end;
    }
    if (pthread_create(&threads[i], NULL, read_file_chunk, &thread_data[i]))
    {
        fprintf(stderr, "Error creating thread\n");
        exit(EXIT_FAILURE);
    }
    last_end = thread_data[i].end;
}
int is_start_at_line_boundary(FILE *file, long start)
{
    if (start == 0)
    {
        return 1; // Start of the file
    }
    fseek(file, start - 1, SEEK_SET);
    if (fgetc(file) == '\n')
    {
        return 1; // Start is at the beginning of a line
    }
    return 0;
}

Функция read_file_chunk будет использовать fseek(), чтобы перейти к началу чанка и прочитать fgets() весь чанк, где она вызывает функцию синтаксического анализа для каждой строки, поскольку каждая строка содержит отдельный json-подобный формат, который вместо , имеет ;, например:

{"created_at": "2020-01-14 12:00:00"; "hashtags": ["A", "B"]; "id": 546542; "uid": 1500}

Должен ли я использовать библиотеку json и не предполагать, что каждая строка является объектом json? Будет ли это более эффективно и разумно?

В вашем посте нет четкого вопроса. Единственное предложение со знаком вопроса: «Может быть, мне стоит только многопоточно обрабатывать каждую строку и другие задачи?» Отредактируйте заголовок и тело, чтобы они содержали четкий вопрос. Как правило, многопоточное чтение одного файла не дает особой выгоды, поскольку несколько потоков будут конкурировать за один ресурс. Многопоточный процесс обработки данных может быть полезным. Однако вы не описываете, какую обработку вы будете выполнять, в частности, каковы последствия работы с фрагментами строк.

Eric Postpischil 27.07.2024 13:47

Пожалуйста, опубликуйте полный пример. Что делает, например, read_file_chunk()? А чтение файла побайтно, чтобы определить, где начинаются и заканчиваются строки, скорее всего, будет настолько медленным, что все возможные выгоды от многопоточности будут потрачены впустую. Вы читаете каждый байт входного файла как минимум дважды, и каждый раз, когда вы вызываете fseek(), вы очищаете буфер и требуете, чтобы данные считывались снова и снова — и большинство реализаций считывают что-то вроде полного буфера размером 8 КБ. Если строки короткие, вы можете читать каждый байт с диска буквально тысячи раз для большого файла.

Andrew Henle 27.07.2024 13:47

А если вы используете один FILE * между потоками, каждый раз, когда какой-либо поток выполняет fseek(), позиция файла для всех остальных потоков также изменяется, и каждый раз, когда поток читает из потока FILE *, читаемые им данные не будут прочтите в любой другой теме.

Andrew Henle 27.07.2024 13:49

Если все строки в файле не имеют одинаковой длины, вы не сможете разделить их до того, как фактически прочитаете файл. Итак, начнем с чтения массива строк. Как только вы это сделаете, вы узнаете, сколько строк имеется, а также получите данные в памяти и сможете равномерно разделить строки между потоками.

Some programmer dude 27.07.2024 13:49

И если все строки файла имеют одинаковый размер, то я рекомендую вместо этого отобразить файл в памяти и передать указатели в потоки.

Some programmer dude 27.07.2024 13:50

@EricPostpischil Я просто предположил, что каждая строка имеет json-подобный объект, и не мог придумать, как обработать фрагмент строки

thuruk9 27.07.2024 14:19

@AndrewHenle Итак, в принципе это просто невозможно реализовать, поскольку это более неэффективно, и если fseek меняет позицию для каждого потока, это на самом деле не работает

thuruk9 27.07.2024 14:21

@Someprogrammerdude Итак, я бы прочитал весь файл в массив, например, с MAX_LINE_SIZE, а затем мог бы разделить его?

thuruk9 27.07.2024 14:23

@thuruk9 Это можно сделать, но нужно быть очень осторожным с тем, как вы это делаете, и если вы используете только один поток FILE *, вы просто не сможете читать один и тот же файл из нескольких потоков одновременно. И вы также должны понимать, что поиск, а затем чтение - это не атомарная операция, поэтому вам нужно либо обеспечить какую-то блокировку взаимного исключения для файла, чтобы каждый поток мог его заблокировать, либо использовать несколько потоков FILE *, либо использовать функции, специфичные для платформы. которые считываются с указанного смещения атомарно, как POSIX pread() или Windows ReadFile(), с использованием OVERLAPPED.

Andrew Henle 27.07.2024 17:15

(продолжение) И, поскольку вы, предположительно, используете многопоточное чтение из соображений производительности, вы должны быть осторожны, чтобы секционирование данных не замедляло работу, чем простое использование одного потока и сохранение простоты.

Andrew Henle 27.07.2024 17:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
10
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я пытаюсь прочитать очень большой файл размером около 100 МБ. Прежде всего, хороший ли это подход или многопоточность следует использовать только для других задач, таких как, например, обработка строки?

Грубо говоря, типичная скорость чтения жесткого диска составляет 150 МБ/с. Для SSD это может быть 400 МБ/с. На фактическую пропускную способность влияет множество факторов, но я думаю, можно с уверенностью предположить, что однопоточное последовательное чтение файла размером 100 МБ не должно занимать более 1 секунды. Это может быть значительно быстрее, если файл «горячий» (кэшируется файловой системой или ОС).

И жесткие диски, и твердотельные накопители являются последовательными устройствами, поэтому даже если у вас есть несколько потоков, пытающихся одновременно читать разные фрагменты файла, каждому потоку, скорее всего, придется ждать чтения других потоков.

В моем эксперименте, если вам нужно прочитать весь файл в память, самый простой способ одновременно является и самым быстрым: выполнить одно чтение для всего файла.

(Отображение файла в памяти может быть хорошей идеей, если вы ожидаете, что вам, возможно, не потребуется помещать весь файл в память и/или вам нужно прочитать файл не по порядку. Кажется, ни то, ни другое не применимо к вашему случаю.)

Остерегайтесь преждевременной оптимизации. Начните с работающей однопоточной реализации.

Если оно недостаточно быстрое, вам придется измерить, чтобы выяснить, где находится узкое место. В вашем случае это может быть время, необходимое для чтения файла в память, или анализ JSON.

Если чтение файла является узким местом, я бы поискал другие решения, кроме многопоточности.

Если синтаксический анализ JSON является узким местом, одним из возможных решений может стать многопоточность. В этом случае я бы сначала оставил однопоточный ввод-вывод как есть и применил бы многопоточный анализ данных.

(Я не отрицаю, что в некоторых случаях вам может потребоваться разбить чтение на части, но я бы не стал с этого начинать.)

Спасибо! Но следует ли мне использовать fgets для чтения файла построчно и последующего его анализа или есть лучший вариант?

thuruk9 27.07.2024 17:49

Я считаю, что вы слишком много об этом думаете и усложняете задачу, чем она есть на самом деле.

Прежде всего, если возможно, используйте стандартный json для вашего файла и существующую библиотеку json для его анализа. 100 МБ на самом деле не так уж и много.

Время, необходимое для чтения файла с диска, затмит время, необходимое для его анализа.

Теперь я представлю, что каждый имеющийся у вас объект необходимо обработать, и это может занять 1 секунду.

Первая реализация, которую вы попробуете, должна заключаться в чтении и анализе файла в одном потоке, а затем в разделении работы для ваших потоков. Если объекты находятся в массиве, вы можете передать указатель начала и счетчик каждому потоку.

Если вы действительно хотите проанализировать файл в разных потоках, я бы все равно прочитал файл в память в одном потоке. Во время чтения подсчитывайте скобки или окончания строк, чтобы определить, где разбить файл. По завершении каждого фрагмента передайте его потоку.

Спасибо. Итак, подход заключается в том, чтобы использовать fread файл сразу? Как я могу затем разбить работу на другие потоки и сохранить фрагмент как массив, содержащий все строки фрагмента?

thuruk9 27.07.2024 17:50

@thuruk9 В основном потоке после чтения просканируйте входной буфер на наличие концов строк, если каждый объект находится на строке. Таким образом, вы сможете выяснить, где разбить файл. Затем передайте указатель на начало каждого фрагмента и размер буфера каждому рабочему процессу.

OfNothing 27.07.2024 18:12

Другие вопросы по теме