Сканирование файла из нескольких точек - PullRequest
1 голос
/ 20 октября 2019

У меня есть файл abc.txt, который содержит алфавит, напечатанный дважды, разделенный символом новой строки

abcdefghijklmopqrstuvwxyz
abcdefghijklmopqrstuvwxyz

Я хотел бы создать парсер, который может анализировать строки одновременно. Например, одна программа на строку. Мой текущий процесс для попытки сделать это:

  • Создать канал для приема строк текста
  • Создать новый сканер для каждой строки
  • Пропустить этот сканер иканал в маршрут
  • Результаты процесса в основном процессе

Однако только один из сканеров возвращает полезный вывод. Код того, что я пытаюсь сделать, таков:

func main() {
    file, err := os.Open("./strangeness/abc.txt")
    if err != nil {
        log.Panic(err)
    }
    defer file.Close()

    inChan := make(chan string)

    for i := 0; i < 2; i++ {
        var scanner scanner.Scanner
        file.Seek(27, 0)

        scanner.Init(file)

        go parseLine(fmt.Sprintf("Scanner %v:", i), &scanner, inChan)
    }

    for msg := range inChan {
        fmt.Println(msg)
    }
}

func parseLine(name string, scanner *scanner.Scanner, out chan string) {
    for i := 0; i < 26; i++ {
        out <- fmt.Sprintf("%s %c", name, scanner.Next())
    }
}

Я думаю, что у меня могут быть некоторые заблуждения относительно того, как работает go text/scanner, или как работают файлы в целом, но я не могуотследить фактический источник ошибки.

Ответы [ 2 ]

1 голос
/ 20 октября 2019

Похоже, проблема в том, что 2 сканера для файла перемещают головку одновременно.

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

package main

import (
    "fmt"
    "log"
    "os"
    "text/scanner"
    "time"
)

func main(){
    var file [2]*os.File
    var err error
    file[0], err = os.Open("./abc.txt")
    file[1], err = os.Open("./abc.txt")
    if err != nil {
        log.Panic(err)
    }
    defer file[0].Close()
    defer file[1].Close()
    var scanner [2]scanner.Scanner
    inChan := make(chan string)


    for i := 0; i < 2; i++ {
        var n int64 = (int64)(i) * 26
        file[i].Seek(n, 0)

        scanner[i].Init(file[i])
        fmt.Println(scanner[0].Pos)
        go parseLine(fmt.Sprintf("Scanner %v:", i), &scanner[i], inChan)
    }

    for msg := range inChan {
        fmt.Println(msg)
    }
}

func parseLine(name string, scanner *scanner.Scanner, out chan string) {
    for i := 0; i < 26; i++ {
        out <- fmt.Sprintf("%s %c", name, scanner.Next())
    }
    time.Sleep(time.Second * 10)
    close(out)
}
0 голосов
/ 20 октября 2019

Среди свойств файла есть одиночная позиция чтения, а не одна для сканера, для потока или для каждой процедуры. Когда вы разделяете один os.File между несколькими объектами сканера (или чем-либо еще), первый, который читает из файла, будет продвигать указатель чтения для любого другого сканера. В вашем примере первая выполняемая программа будет читать строку из файла, а вторая пытается прочитать, но уже находится в конце файла.

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

Это хорошее время, чтобы подумать о параллелизме, и если это действительно стоит затраченных усилий. Самая медленная часть этой программы, безусловно, будет фактически читать файл с диска, и нетколичество горутин сделает это быстрее. Дополнительная оптимальная копия буфера не требуется в оптимальной однопоточной программе, и синхронизация с обычными процессами также требует больших затрат. Моя интуиция заключается в том, что использование здесь подпрограмм делает программу намного более сложной и потенциально медленной.

Основное изменение, которое я внесу в вашу программу, заключается в том, что только одна вещь читает файл;например, основной цикл в вашей программе. Пакет bufio включает в себя объект Scanner, который может одновременно считывать файл и создавать скопированную строку. Я бы однажды запустил пул рабочих подпрограмм, а затем добавил в них строки:

lines := make(chan string)
file, err := os.Open("./strangeness/abc.txt")
if err != nil { ... }

scan := bufio.NewScanner(file)
for scan.Scan() {
    lines <- scan.Text()
}
close(lines)

https://play.golang.org/p/o7UwZrgfVy7 имеет более полный пример, который компилируется. Обратите внимание, что структура подпрограммы требует некоторых рассуждений: я запустил отдельную подпрограмму для обработки результатов, и у меня есть отдельный канал для оповещения о его завершении.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...