Go language Bible - Chapter 8 Goroutines and Channels - 8.5 concurrent loops

Posted by straycat on Wed, 12 Jan 2022 19:11:13 +0100

Chapter 8 Goroutines and Channels

Concurrent programs in Go language can be implemented by two means: goroutine and channel, which support sequential communication processes, or CSP for short. CSP is a concurrent programming model. In this concurrent programming model, values will be passed in different running instances. The second means is multi-threaded shared memory

8.5 concurrent loops

In this section, let's take a look at the common concurrency model of circular iteration in parallel. We generate some thumbnails from full-size images. The following function can help us stretch the images. We can use it directly

package thumbnail

func ImageFile(infile string) (string error)

Now let's build this function and call it makethumbnails

func makeThumbnails(filenames []string) (string, error) {
	for _, f := range filenames {
		if _, err := thumbnail.ImageFile(f); err != nil {
			log.Println(err)
		}
	}
}

Each of the above file s is independent. We use the go keyword to put each processing into goroutine. Note that we have no processing errors below

func makeThumbnails(filenames []string) (string, error) {
	for _, f := range filenames {
		go thumbnail.ImageFile(f)
	}
}

In this way, all the above file s are parallel, and when one goroutine completes first, makethumbnails will return. No matter whether other goroutines end or not, we now use the communication mechanism of channel to end the execution of all goroutines

func makeThumbnails(filenames []string) (string, error) {
	for _, f := range filenames {
		ch := make(chan struct{}) //Create a channel
		go func(f string) {       //Give the traversed function as the parameter of goroutine
			thumbnail.ImageFile(f)  //At the end of the goroutine function, a message is sent to the channel (here is the structure)
			ch <- struct{}{}
		}(f)
		for range filenames {
			<- ch                   //The receiver is at the end of the main function, which can ensure that all goroutine s end, because the program will not stop when the channel does not receive a value
		}
	}
}

In the above function, we call the function in goroutine. It is the case that the function will fail to call. Now we use channel technology to transfer possible errors from goroutine.

func makeThumbnails(filenames []string) (string, error) {
	for _, f := range filenames {
		errors := make(chan error)
		go func(f string) {
			_, err := thumbnail.ImageFile(f)    //Keep returned errors
			errors <- err                       //Send errors
		}(f)
	}
	for range filenames {
		if err := <- errors;err != nil {        //Receive error, return if any
			return err
		}
	}
	return nil                                //If not, it returns a null value
}

Now we can pass the errors that may occur when calling the function, but there is a problem with this program. Because we pass an error and the error of this channel is not received (the assignment here to the new variable err is not received), the channel will be blocked. In the future, even if there are errors in goroutine, the error cannot be successfully passed, Now let's create a new channel with cache, and let the function return appropriate information whether the processing is successful or not

func makeThumbnails(filenames []string) (thumbfiles []string, err error) {

	type item struct { //First define a structure. In order to integrate the return value into a variable, the internal members of the structure are two return values
		thumbfile string
		err       error
	}

	ch := make(chan item, len(filenames))    //Define a cached channel

	for _, f := range filenames {
		go func(f string) {
			var it item                     //Define a variable of structure type to call
			it.thumbfile, it.err = thumbnail.ImageFile(f)
			ch <- it
		}(f)
	}

	for range filenames {
		it := <- ch
		if it.err != nil {
			return nil,it.err
		}
		thumbfiles = append(thumbfiles,it.thumbfile)
	}
	return thumbfiles,nil
}

The above program is perfect, but we still want to change it. We want to get the number of files. Here we count each goroutine, and we use a new type sync Waitgroup, look at the code

func makeThumbnails(filenames <-chan string) int64 {
	
	sizes := make(chan int64)
	var wg sync.WaitGroup
	
	for f := range filenames {
		wg.Add(1)
		go func(f string) {
			defer wg.Done()
			thumb, err := thumbnail.ImageFile(f)
			if err != nil {
				log.Println(err)
				return
			}
			info, _ := os.Stat(err)
			sizes <- info.Size()
		}(f)
	}

	go func() {
		wg.Wait()
		close(sizes)
	}()

	var total int64
	
	for sise := range sizes {
		total += size
	}
	
	return total
}

The above program code structure is very common and authentic when we use concurrent loops but don't know the number of iterations

Topics: Go Back-end goroutine