Do not communicate by sharing memory;instead, share memory by communicate.不要通过共享内存来通信,相反,应该通过通信来共享内存。这是Go语言并发的哲学座右铭。每个go开发在学习channel之前,应该先理解这个原则。

单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。

虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。

Go语言的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。

如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。

Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。(以上内容引自书籍并发编程对channel的解释)

数据结构

发送数据的过程

接收数据的过程

如何安全的关闭chan

Notification

1.mallocgc和malloc调用底层函数brk,bbrk做了什么

2.源码中的quick path如何理解,可以快速排除异常情况

3.sudoG是什么,双层缓存sudog是为了做什么

4.gopark。goready大概做了什么

5.如何安全的关闭chan

如何安全的关闭chan

  • 无缓冲的channel,同一个协程内读写,会导致all goroutine are asleep.dead lock
  • 无缓冲的channel,通道的同步写早于读channel
  • 从一个没有数据的channel里拿数据引起的死锁
  • 循环等待引起的死锁,两个G互相持有对方拥有的资源,无法读写
  • 有缓冲区,收发在同一个G,但是缓冲区已满,写阻塞
  • 有缓冲区,读空的channel,读阻塞,可以加select控制

读写channel哪个先关。

关闭channel的原则,不要让receiver来关闭chan。也不要在多个sender的时候由sender关闭chan。会导致panic的情况有两个。一个是给已经关闭的chan写数据。另一个是重复close chan会导致panic。一些常见的方式,可以使用sync.Once和sync.mutex来关闭chan。还可以直接用panic和recovery来处理panic。

  • 一读一写(只要一个写都可以写端关闭)。写端关闭。不写数据的时候,关闭chan,通知读端。
  • 一个读多个写。读端通知写端关闭。可以新加个close chan,通知写端不要输入了。
  • 多读多写。增加toStop chan去通知关闭close chan。读写端会有select检查close chan的状态。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
func main() {
	rand.Seed(time.Now().UnixNano())
	log.SetFlags(0)

	// ...
	const MaxRandomNumber = 100000
	const NumReceivers = 10
	const NumSenders = 1000

	wgReceivers := sync.WaitGroup{}
	wgReceivers.Add(NumReceivers)

	// ...
	dataCh := make(chan int, 100)
	stopCh := make(chan struct{})
	// stopCh is an additional signal channel.
	// Its sender is the moderator goroutine shown below.
	// Its reveivers are all senders and receivers of dataCh.
	toStop := make(chan string, 1)
	// the channel toStop is used to notify the moderator
	// to close the additional signal channel (stopCh).
	// Its senders are any senders and receivers of dataCh.
	// Its reveiver is the moderator goroutine shown below.

	var stoppedBy string

	// moderator
	go func() {
		stoppedBy = <-toStop // part of the trick used to notify the moderator
		// to close the additional signal channel.
		close(stopCh)
	}()

	// senders
	for i := 0; i < NumSenders; i++ {
		go func(id string) {
			for {
				Val := rand.Intn(MaxRandomNumber)
				if Val == 0 {
					// here, a trick is used to notify the moderator
					// to close the additional signal channel.
					select {
					case toStop <- "sender#" + id:
					default:
					}
					return
				}

				// the first select here is to try to exit the
				// goroutine as early as possible.
				select {
				case <-stopCh:
					return
				default:
				}

				select {
				case <-stopCh:
					return
				case dataCh <- Val:
				}
			}
		}(strconv.Itoa(i))
	}

	// receivers
	for i := 0; i < NumReceivers; i++ {
		go func(id string) {
			defer wgReceivers.Done()

			for {
				// same as senders, the first select here is to
				// try to exit the goroutine as early as possible.
				select {
				case <-stopCh:
					return
				default:
				}

				select {
				case <-stopCh:
					return
				case Val := <-dataCh:
					if Val == MaxRandomNumber-1 {
						// the same trick is used to notify the moderator
						// to close the additional signal channel.
						select {
						case toStop <- "receiver#" + id:
						default:
						}
						return
					}

					log.Println(Val)
				}
			}
		}(strconv.Itoa(i))
	}

	// ...
	wgReceivers.Wait()
	log.Println("stopped by", stoppedBy)
}