Hi folks and a Happy new Year!

Today, I would like to show you some interesting things you can do with channels. Consider the following simple example.

package main

import "fmt"

func main() {
	generatedPassword := make(chan int, 100)
	correctPassword := make(chan int)
	defer close(generatedPassword)
	defer close(correctPassword)
	go passwordIncrement(generatedPassword)
	go checkPassword(generatedPassword, correctPassword)
	pass := <-correctPassword
	fmt.Println(pass)
}

func checkPassword(input <-chan int, output chan<- int) {
	for {
		p := <-input
		//Introduce lengthy operation here
		// time.Sleep(time.Second)
		fmt.Println("Checking p:", p)
		if p > 100000 {
			output <- p
		}
	}
}

func passwordIncrement(out chan<- int) {
	p := 0
	for {
		p++
		out <- p
	}
}

The premise is as follows. It launches two go routines. One, which generates passwords, and an other which checks for validity. The two routines talk to each other through the channel generatedPassword. That’s the providing connections between them. The channel correctPassword provides output for the checkPassword routine.

If there is data received from correctPassword channel, we found our first password and there is no need to look further so we, print the password and quit. The channels will close with defer. This works. But the password is usually either a []byte or a string. With string, it still works.

package main

import "fmt"

func main() {
	generatedPassword := make(chan string, 100)
	correctPassword := make(chan string)
	defer close(generatedPassword)
	defer close(correctPassword)
	go passwordIncrement(generatedPassword)
	go checkPassword(generatedPassword, correctPassword)
	pass := <-correctPassword
	fmt.Println(pass)
}

func checkPassword(input <-chan string, output chan<- string) {
	for {
		p := <-input
		//Introduce lengthy operation here
		// time.Sleep(time.Second)
		fmt.Println("Checking p:", p)
		if performSomeCheckingOperation(p) {
			output <- p
		}
	}
}

func generateNewPassword(out chan<- string) {
	var p string
	for {
		p = generate(p)
		out <- p
	}
}

The generating happens based on the previously generated password. For example, we increment, or permeate. aaaa, aaab, aaac…

So generatedPassword is a buffered channel, it gathers a 100 passwords from which checking retrieves passwords one by one and works on them in a slower process.

Now, this is fine, but using []byte arrays will always be more powerful and faster. So we would like to use []byte. Like this:

package main

import "fmt"

func main() {
	generatedPassword := make(chan []byte, 100)
	correctPassword := make(chan []byte)
	defer close(generatedPassword)
	defer close(correctPassword)
	go passwordIncrement(generatedPassword)
	go checkPassword(generatedPassword, correctPassword)
	pass := <-correctPassword
	fmt.Println(string(pass))
}

func checkPassword(input <-chan []byte, output chan<- []byte) {
	for {
		p := <-input
		//Introduce lengthy operation here
		// time.Sleep(time.Second)
		fmt.Println("Checking p:", string(p))
		if performSomeCheckingOperation(p) {
			output <- p
		}
	}
}

func generateNewPassword(out chan<- []byte) {
	var p []byte
	for {
		p = generate(p)
		out <- p
	}
}

This will not work. Why? Because []byte is a slice and thus will be constantly overwritten. The checking go routine will always only check the last data and many generated passwords will be lost. This is also noted in go’s scanner here => Scanner.Bytes

We have a couple of options here.

We could use string channels and convert to []byte after. This is still okay, because the conversion isn’t very CPU intensive.

...
generatedPassword := make(chan string, 100)
correctPassword := make(chan string)
...
p := []byte(<-input) //This will work very nicely.
...

Options two would be If you have a fixed password to handle, fix data, for example MD5 hash, you can use a byte array. Like this:

package main

import "fmt"

const PASSWD=13

func main() {
	generatedPassword := make(chan [PASSWD]byte, 100)
	correctPassword := make(chan [PASSWD]byte)
	defer close(generatedPassword)
	defer close(correctPassword)
	go passwordIncrement(generatedPassword)
	go checkPassword(generatedPassword, correctPassword)
	pass := <-correctPassword
	fmt.Println(string(pass))
}

func checkPassword(input <-chan [PASSWD]byte, output chan<- [PASSWD]byte) {
	for {
		p := <-input
		//Introduce lengthy operation here
		// time.Sleep(time.Second)
		fmt.Println("Checking p:", string(p))
		if performSomeCheckingOperation(p) {
			output <- p
		}
	}
}

func generateNewPassword(out chan<- [PASSWD]byte) {
	var p [PASSWD]byte
	for {
		p = generate(p)
		out <- p
	}
}

This is also one solution. If you have to convert between the two, could go with p := byte[:].

Conclusion is, that use conversion rather than string types and be aware that using slices in channels is dangerous.

Thanks for reading! Gergely.