Goroutines和Channels

Go language in concurrent programs can be achieved by two means, first is the traditional model of concurrency, multi-threaded shared memory, and the second is the modern concurrency model, communication sequential processes (CSP), Go and language goroutine channel order to support the communication process.


A, goroutines
1. Go in the language, each of the concurrent execution unit is called a goroutine.
2. main goroutine: When a program starts, i.e. the main function running in a separate goroutine, called main goroutine.
3. goroutine create: new goroutine use go statement to create. Syntactically, go with the keyword go before the statement is a common function or method call. go statement that it will function in a statement run goroutine newly created, and go statement itself will be completed quickly.

4. When the main function to return, all goroutine will be directly interrupted, the program exits.


Two, Channels
If goroutines is then complicated by body language Go program, then the channels is the communication mechanism between them.
Through channels, a goroutine value information may be sent to another goroutine.
Each type of value that can be transmitted is only one channel.
1. channels created

channel is a reference type variable.
2. channels compare
two of the same channel type can be compared using == operator. If the two channel refer to the same object, then the result of the comparison is true. A channel can also be compared and nil.
3. channels for transmitting and receiving data

Do not use a receiver to receive the results of operations are also legal.
4. channels Close

Based on any transmission operation has been closed channel will result in panic exception.
Based on the channel has been closed before performing a data receiving operation has been transmitted can still be received successfully, if the channel has no data, the subsequent reception operation will no longer blocks, but instead immediately returns a zero value.
5. channels unbuffered
a transmission channel based unbuffered operation will cause the sender goroutine blocked until another goroutine reception operation is performed on the same channel, when the value of the transmission after the successful transmission through the channel, in order to continue two goroutine behind the implementation of the statement. Conversely, if the receiving operation occurs first, then the receiver will goroutine blocked until another goroutine operations performed on the same transmission channel.
Based unbuffered channel transmit and receive operations will result in two goroutine do a synchronization, for this reason, the cache no channel is sometimes also called a Synchronization channel.
NOTE:
Some event message does not carry additional information, they are merely used as a synchronization between the two goroutines, which can be used when struct {} air type structure is used as an element of Channels.
6. A series of channels (Pipeline)
channels may also be used to link together a plurality goroutine, a channel as input the output of the next channel. This series of channels is called the pipeline (pipeline).
Channel 7. unidirectional
chan <- int: int send only the channel, the transmission can not be received
<-chan int: int only receives the channel, can only receive not send
this restriction will be detected at compile time.
Note:
Because the close operation only for the assertion no longer send new data to the channel, so only the sender is located in goroutine will call the close function, so the channel receives only a close call would be a compilation error.
Any two-way to one-way channel channel assignment variables will result in implicit conversion, the conversion from two-way channel for the one-way channel. Syntax reverse conversion does not exist.
Channels 8. unbuffered
inner channel with a holding element of the queue buffer.

internal cache capacity channel: (CH) CAP
channel internal cache length: len (ch)
is inserted inside the element to which the tail of the transmission buffer queue operation based on the buffered channel, the receiving operation is to remove elements from the head of the queue. If the internal buffer queue is full, it sends will be blocked until another goroutine reception operation is performed and the release of a new queue space. Conversely, if the channel is empty, the operation will be blocked until the reception of another transmission goroutine perform operations to insert elements into the queue.
decoupling channel reception buffer queue and goroutine transmitted.


三、并发的循环
1. goroutine泄露
某个channel的内部缓存队列满了,但是没有goroutine向这个channel执行接收操作,从而导致向这个channel发送值的所有goroutine都永远阻塞下去,并且永远都不会退出。这种情况,称为goroutine泄露,这将是一个bug。和垃圾变量不同,泄露的goroutine并不会被自动回收,因此确保每个不再需要的goroutine都能正常退出是很重要的。
2. sync.WaitGroup
有时候,我们需要多个goroutine都运行结束以后做一些事情,比如关闭一个channel。Go语言提供了一种特殊的计数器,用于检测多个goroutine是否都已运行结束。它在每一个goroutine启动时自增1,在每一个goroutine退出时自减1,且会一直等待直至计数器自减为0,即表示所有goroutine都已运行结束。
使用示例:

func makeThumbnails(filenames <-chan string) int64 {
	sizes := make(chan int64)
	var	wg sync.WaitGroup//number of working goroutines
	for	f := range filenames {
		wg.Add(1)//必需在worker goroutines开始之前调用,才能保证Add()是在closer goroutine调用Wait()之前被调用
		//worker goroutines
		go func(f string) {
			defer wg.Done()//使用defer来确保计数器即使是在程序出错的情况下依然能够正确地自减
			thumb, err := thumbnail.ImageFile(f)
			if err != nil {
				log.Println(err)
				return
			}
			info, _ := os.Stat(thumb)
			sizes <- info.Size()
		}(f)
	}
	//closer goroutine
	go func() {
		wg.Wait()
		close(sizes)
	}()
	var	total int64
	for	size := range sizes {
		total += size
	}
	return total
}

 

四、基于select的多路复用
有时候我们需要等待多个channel的其中一个返回事件,但是我们又无法做到从每一个channel中接收信息。假如我们试图从其中一个channel中接收信息,而这个channel又没有返回信息,那么程序会立刻被阻塞,从而无法收到其他channel返回的信息。这时,基于select的多路复用就派上用场了。

select会一直等待直至有能够执行的case分支才去执行这个case分支。当条件满足时,select才会去通信并执行case之后的语句;这时候其它通信是不会执行的。一个没有任何case的select语句写作select{},会永远地等待下去。
如果多个case同时就绪时,select会随机地选择一个执行,这样来保证每一个channel都有平等的被select的机会。
channel的零值是nil,对一个nil的channel发送和接收操作会永远阻塞,在select语句中操作nil的channel永远都不会被select到。

 

五、消息广播
有时候,我们需要将某个消息广播通知所有运行中的goroutine,但是我们又无法知道goroutine的数量,这时候可以使用这种策略:创建一个空的channel,将从这个channel中接收信息的操作加入基于select的多路复用,由于这时channel是空的,所有的goroutine都无法从中接收到值;当我们需要广播消息的时候,关闭这个channel,这样所有的goroutine都能从中接收到零值,以此表示该消息已传达。(一个channel只能用于表达一种消息)

 

六、一个综合运用的实例程序——并发的字典遍历

/**
*广度遍历目录文件,计算文件数量与大小
*每隔0.5秒输出已计算的文件数量与大小
*当标准输入有值时,中断程序运行
**/
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"time"
)

//广度遍历目录文件,计算文件数量与大小
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
	defer n.Done()
	if cancelled() {
		return
	}
	for _, entry := range dirents(dir) {
		if entry.IsDir() {
			n.Add(1)
			subdir := filepath.Join(dir, entry.Name())
			go walkDir(subdir, n, fileSizes)
		} else {
			fileSizes <- entry.Size()
		}
	}
}

/*
*目的:控制同一时刻打开的文件数量,防止程序占用太多资源
*实现:创建一个带缓存的channel,通过channel的缓存队列大小控制同一时刻打开的文件数量。
*读取文件之前向该channel发送一个信号量,读取完以后向该channel接收一个信号量,若是channel的缓存
*队列满了,则会阻塞,无法继续打开文件。
 */
var sema = make(chan struct{}, 2)

func dirents(dir string) []os.FileInfo {
	select {
	case sema <- struct{}{}:
	case <-done:
		return nil
	}
	defer func() { <-sema }()
	entries, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprint(os.Stderr, "du1: %\n", err)
		return nil
	}
	return entries
}

/*
*目的:读取标准输入,只要有值则停止运行程序
*实现:创建一个空channel,每个goroutine读取文件之前都先从channel中接收值,如果阻塞,表示程序可以继续运行,
*当标准输入有值时,关闭该channel,所有未读取文件的goroutine从channel中接收到零值,中断程序运行。
 */
var done = make(chan struct{})

func cancelled() bool {
	select {
	case <-done:
		return true
	default:
		return false
	}
}

func main() {
	//读取标准输入,只要有输入则停止程序
	go func() {
		os.Stdin.Read(make([]byte, 1))
		close(done)
	}()

	flag.Parse()
	roots := flag.Args()
	if len(roots) == 0 {
		roots = []string{"."}
	}
	//用于发送文件大小的channel
	fileSizes := make(chan int64)

	/*
	*目的:当读取文件大小的所有goroutine都结束运行时,关闭用于发送文件大小的channel
	*实现:使用特殊计数器sync.WaitGroup,每次新增一个goroutine则自增1,每次结束一个goroutine
	*则自减1,当其自减为0时,表示所有goroutine都已结束运行,可以关闭channel了
	 */
	var n sync.WaitGroup
	for _, root := range roots {
		n.Add(1)
		go walkDir(root, &n, fileSizes)
	}
	go func() {
		n.Wait()
		close(fileSizes)
	}()

	/*
	*目的:每隔0.1秒输出文件数量与大小计算的结果
	*实现:使用time.Tick函数。time.Tick函数返回一个channel,程序会周期性地像一个节拍器一样向这个channel发送事件,
	*每一个事件的值是一个时间戳。当从该channel中接收到值的时候,就可以打印了。
	 */
	var tick <-chan time.Time
	tick = time.Tick(100 * time.Millisecond)

	var nfiles, nbytes int64
loop:
	for {
		select {
		case <-done:
			/*
			*程序结束,需要先吧fileSizes这个channel中的内容排空,以保证对walkDir的调用不会被向fileSizes发送信息阻塞住,
			*可以正确地完成,防止goroutine泄露。
			 */
			for range fileSizes {
			}
		case size, ok := <-fileSizes:
			if !ok { //fileSizes这个channel已关闭,退出循环loop
				break loop
			}
			nfiles++
			nbytes += size
		case <-tick:
			printDiskUsage(nfiles, nbytes)
		}
	}
}

//打印已经计算的文件数量与大小
func printDiskUsage(nfiles, nbytes int64) {
	fmt.Printf("%d files  %.1f GB\n", nfiles, float64(nbytes)/1e9)
}

 

Guess you like

Origin www.cnblogs.com/wujuntian/p/11241171.html