Contents

Let's talk about gorutines

I have to say that my relationship with the goruntins not is the bests, However, I am deeply attracted to gorutins.

Ok, let’s skip the romantic part and get down to the nitty-gritty.

I have a dream

I have a dream? Yes, this question should be “I have a problem”, but it’s not enought funny to admit that I do.

In an ideal world I would have a lot of heavy tasks, running concurrently that I could cancel at any time, without worrying about memory leaks, race conditions or synchronization scheduling. Anyway, I have to think about it.

Dilema

Suppose I need to find a word (password), and I know, that it can be found in many password dictionaries, but I don’t have time to go through a dictionary one by one.

I will explain it in more detail in the code. Take it easy!

Go to code

We need to make these dictionaries accessible by name.

/posts/lets_talk_about_gorutines/directory.png

fs, err := os.ReadDir("./")
if err != nil {
    panic(err)
}

dictionaries := make([]string, 0, len(fs)-1)
for _, f := range fs {
    if f.Name() != "main.go" {
        dictionaries = append(dictionaries, f.Name())
    }
}

Are you ready?

At this point things start to get complicated. Although there are many ways to do it, this way is quite illustrative.

We need a for and a select to capture when something significant to the purpose of our program has occurred. Either the context has closed or a goruntine has already found the word we were looking for.

// we have to wait for at least one goruntine to find the word.
wg := sync.WaitGroup{}
wg.Add(1)

// channel to receive the dictionary
out := make(chan string)
// create context to cancel gorutins
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context, wg *sync.WaitGroup) {
    for {
        select {
        case <-ctx.Done():
            log.Println("Close another goruntines")
            return
        case dictionary := <-out:
            wg.Done()
            log.Println("WINNER DICTIONARY: ", dictionary)
            cancel()
        }
    }
}(ctx, &wg)

for _, p := range dictionaries {
    go Search(ctx, "andrew", p, out) // Set path
}

wg.Wait()
log.Println("Program finished")

You can read it carefully, it doesn’t look like it, but there is a lot here.

Finally, the function does all the work.

func Search(ctx context.Context, target, path string, out chan string) {
	readFile, err := os.Open(path)
	if err != nil {
		fmt.Printf("cannot open file on path %s - error %v", path, err)
		return
	}

	fileScanner := bufio.NewScanner(readFile)
	fileScanner.Split(bufio.ScanLines)
	for fileScanner.Scan() {
		// time.Sleep(time.Second * 1) // You can uncomment this linea to see this process in more details

		// add sleep to simulate a to too large file
		log.Println("[+] - ", path, " Palabra: ", fileScanner.Text())
		if fileScanner.Text() == target {
			out <- path
			break
		}
	}
}

Run and conclusion

If you have fun programming this will be a piece of cake, goruntines require a lot of practice and dedication. I have actually practiced with this article. You can see the repo github.com/juanmachuca95/search_go

This would be the final result

go run main.go

/posts/lets_talk_about_gorutines/output.png

I am very new to writing articles, send me your feedback to keep improving.

Thank you so much, for visiting me.

Happy code!!