Pierwsze założenie było takie, żeby każdy folder przetwarzany był w osobnej gorutynie i zwracał wyniki kanałem, bądź porostu wypisywał je na standardowym wyjściu.
Program działał całkiem fajnie i wydajnie przy małej ilości plików do przeszukania. Kiedy natomiast uruchomiłem go dla własnego katalogu HOME, lub dysku C na windowsie, po chwili produkował sporą ilość błędów wynikającą ze zbyt dużej liczby uchwytów do plików. Okazało się ze otwieranie i przetwarzanie katalogu w osobnym procesie powodowało lawinowy przyrost otwartych plików, z którymi zarówno Ubuntu jak i Windows7 nie były sobie w stanie poradzić.
Wróciłem więc i przeprojektowałem pogram w ten sposób, aby pliku otwierane były tylko w głównej gorutynie, natomiast proces dopasowywania nazw plików do wyrażenia regularnego odbywał się w osobnych procesach.
Zaczynając od początku. Tworzymy strukturę będzie przechowywać konfigurację naszego przeszukiwania, czyli katalog od którego startujemy, wyrażenie którego szukamy. Struktura w Go pełni rolę swego rodzaju obiektu. Posiada zaalokowaną pamięć oraz swoje atrybuty. Przypomnieć można, że jedynie atrybuty, które zaczynają się wielgachną literą będą widoczne poza strukturą, czyli można powiedzieć, publiczne.
------------------------------
Następnie przygotowujemy funkcje konstruującą. W Go nie istnieją znane z innych języków konstruktory, dlatego często tworzy się funkcje, które inicjują i zwracają wskaźnik do nowego obszaru pamięci zarezerwowanej przez strukturę. Nazwy w Go powinny być jednoznaczne, możliwie krótkie i oczywiście znaczące. Wszystko w myśl zasady KISS you FOOL.
W naszym przypadku funkcja taka mogła by mieć postać:------------------------------
type Config struct {
StartPath string
SearchNamePattern *regexp.Regexp
SearchContentPattern *regexp.Regexp
}------------------------------
Następnie przygotowujemy funkcje konstruującą. W Go nie istnieją znane z innych języków konstruktory, dlatego często tworzy się funkcje, które inicjują i zwracają wskaźnik do nowego obszaru pamięci zarezerwowanej przez strukturę. Nazwy w Go powinny być jednoznaczne, możliwie krótkie i oczywiście znaczące. Wszystko w myśl zasady KISS you FOOL.
------------------------------
func NewConfig(startDir string, snp string) Config {
snpRe := regexp.MustCompile(snp)
return Config{startDir, snpRe}
}------------------------------
Funkcja zaczyna się wielgachną literą więc będzie widoczna na zewnątrz modułu. Skoro jednak nasza struktura również zaczyna się od dużej litery, alternatywnym sposobem utworzenia naszego konfiga byłoby: return Config{startDir, snpRe}.
Przygotujmy teraz funkcje odczytujące zawartość katalogu i przetwarzające listę plików z niego. Prawdziwe wąskie gardło naszej aplikacji.
------------------------------
//
// Process directory given as a path
//
func processDir(p string) []os.FileInfo {
var (
f *os.File
fi []os.FileInfo
err error
)
if f, err = os.Open(p); err != nil {
return fi
}
defer f.Close()
if fi, err = f.Readdir(-1); err != nil {
return fi
}
return fi
}
//
// Proces list of file infos to search re in files
//
func processList(basePath string, list []os.FileInfo, re *regexp.Regexp, namesCh, dirCh, processedCh chan string) {
for _, fileInfo := range list {
if fileInfo.IsDir() {
dirCh <- path.Join(basePath, fileInfo.Name())
}
if re.MatchString(fileInfo.Name()) {
namesCh <- path.Join(basePath, fileInfo.Name())
}
}
processedCh <- basePath
}------------------------------
I tu zaczynamy używać kanałów.
Kanał namesCh przekazuje informacje o plikach których nazwy spełniają kryterium.
Do kanału dirCh zapisujemy ścieżkę do katalogu jeśli takowy znajdziemy.
Katalog processedCh informuje nas, że skończyliśmy przetwarzać katalog o nazwie basePath.
Poniżej zaś implementacja głównej procedury. Ta wersja gromadzi listę plików w splice. Zwraca pełną listę wyników.
Do monitorowania, które katalogi zostały już przetworzone służy mapa balancer. Kiedy będzie ona pusta oznacza to, że nie ma więcej katalogów do przetworzenia.
------------------------------
func process(conf Config, namesCh, dirCh, processedCh chan string) []string {
startPath := conf.StartPath
re := conf.SearchNamePattern
fi := processDir(startPath)
balancer := make(map[string]bool, 10)
var fileList []string
go processList(startPath, fi, re, namesCh, dirCh, processedCh)
balancer[startPath] = true
for {
if len(balancer) <= 0 {
break
}
select {
case dir := <-dirCh:
fi := processDir(dir)
go processList(dir, fi, re, namesCh, dirCh, processedCh)
balancer[dir] = true
case dir := <-processedCh:
delete(balancer, dir)
case fpath := <-namesCh:
fileList = append(fileList, fpath)
}
}
close(namesCh)
return fileList
}------------------------------
Dodajmy jeszcze główną funkcję, która będzie wyeksportowana w pakiecie.
------------------------------
//
// Func returns list of files which maches to patterns from conf
//
func Find(conf Config) []string {
// Chanel to recive names of matching files
var namesCh = make(chan string)
// Chanel to recive names of dir names
var dirCh = make(chan string)
// chanel to recive finished dirs
var processedCh = make(chan string)
files := process(conf, namesCh, dirCh, processedCh)
return files
}------------------------------
A teraz pomyślmy dlaczego nasz program który korzysta z naszej biblioteki musi czekać aż skończymy przetwarzać wszystkie pliki? Czemu nie może przekazać do funkcji kanału i pobierać z niego pasujące pliki kiedy tylko zostaną one zapisane do kanału? Ależ oczywiście że może. Co pokazuje poniższa implementacja.
------------------------------
//
// Function gets channel as parameters. Sends to outCh files
// which matches to patters from conf
func GoFind(conf Config, outCh chan string) {
// Chanel to recive names of dir names
var dirCh = make(chan string)
// chanel to recive finished dirs
var processedCh = make(chan string)
goprocess(conf, outCh, dirCh, processedCh)
}
//
//
//
func goprocess(conf Config, namesCh, dirCh, processedCh chan string) {
startPath := conf.StartPath
re := conf.SearchNamePattern
fi := processDir(startPath)
balancer := make(map[string]bool, 10)
go processList(startPath, fi, re, namesCh, dirCh, processedCh)
balancer[startPath] = true
for {
if len(balancer) <= 0 {
break
}
select {
case dir := <-dirCh:
fi := processDir(dir)
go processList(dir, fi, re, namesCh, dirCh, processedCh)
balancer[dir] = true
case dir := <-processedCh:
delete(balancer, dir)
}
}
close(namesCh)
}------------------------------
Brak komentarzy:
Prześlij komentarz