Go语言并发

Go语言并发

1、goroutine

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

// goroutine语法格式
go 函数名(参数列表)
// 例如开启一个新的goroutine
go f(x,y,z)

Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个

函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

注意:go关键字后面必须跟一个函数,不能是语句或其他东西,函数的返回值被忽略。

1.1 通过go+匿名函数形式启动goroutine

package main

import (
	"runtime"
	"time"
)

func main() {
	// 结果
	// NumGoroutine= 2
	// 49995000
	go func() {
		sum := 0
		for i := 0; i < 10000; i++ {
			sum += i
		}
		println(sum)
		time.Sleep(1 * time.Second)
	}()
	// NumGoroutine可以返回当前程序的goroutine数目
	println("NumGoroutine=", runtime.NumGoroutine())
	// main goroutine故意sleep5秒,防止其提前退出
	time.Sleep(5 * time.Second)
}

1.2 通过go+有名函数形式启动goroutine

package main

import (
	"runtime"
	"time"
)

func sum() {
	sum := 0
	for i := 0; i < 10000; i++ {
		sum += i
	}
	println(sum)
	time.Sleep(1 * time.Second)
}

func main() {
	// 结果
	// NumGoroutine= 2
	// 49995000
	go sum()
	//NumGoroutine可以返回当前程序的goroutine数目
	println("NumGoroutine=", runtime.NumGoroutine())
	//main goroutine故意sleep5秒,防止其提前退出
	time.Sleep(5 * time.Second)
}
package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}
# 输出的hello和world是没有固定先后顺序,因为它们是两个goroutine在执行
world
hello
hello
world
world
hello
hello
world
world
hello

goroutine 有如下特性:

  • go的执行是非阻塞的,不会等待。

  • go后面的函数的返回值会被忽略。

  • 调度器不能保证多个goroutine的执行次序。

  • 没有父子goroutine的概念,所有的goroutine是平等地被调度和执行的。

  • Go程序在执行时会单独为main函数创建一个goroutine,遇到其他go关键字时再去创建其他的

    goroutine。

  • Go没有暴露goroutine id给用户,所以不能在一个goroutine里面显式地操作另一个goroutine,不过

    runtime包提供了一些函数访问和设置goroutine的相关信息。

1.3 func GOMAXPROCS

func GOMAXPROCS(n int) int 用来设置或查询可以并发执行的 goroutine 数目,n大于1表示设置

GOMAXPROCS 值,否则表示查询当前的 GOMAXPROCS 值。例如:

package main

import "runtime"

func main() {
	// 结果
	// GOMAXPROCS= 8
	// GOMAXPROCS= 2
	// 获取当前GOMAXPROCS的值
	println("GOMAXPROCS=", runtime.GOMAXPROCS(0))
	// 设置GOMAXPROCS的值为2
	runtime.GOMAXPROCS(2)
	// 获取当前GOMAXPROCS的值
	println("GOMAXPROCS=", runtime.GOMAXPROCS(0))
}

1.4 func Goexit

func Goexit() 是结束当前 goroutine 的运行,Goexit 在结束当前 goroutine 运行之前会调用当前

goroutine 已经注册的 defer。Goexit 并不会产生 panic,所以该 goroutine defer 里面的 recover 调用都返回

nil。

package main

import (
	"fmt"
	"runtime"
	"time"
)

func exit() {
	// 在函数退出前执行
	defer fmt.Println("B")
	// 退出所在子协程
	runtime.Goexit()
	fmt.Println("C")
}

func main() {
	go func() {
		fmt.Println("A")
		exit()
		fmt.Println("D")
	}()
	go func() {
		fmt.Println("F")
	}()
	time.Sleep(time.Second * 2)
	fmt.Println("E")
}
# 输出
F
A
B
E

1.5 func Gosched

func Gosched()是放弃当前调度执行机会,将当前 goroutine 放到队列中等待下次被调度。

只有 goroutine 还是不够的,多个 goroutine 之间还需要通信、同步、协同等,这些内容在后面逐个介绍。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	go func() {
		for i := 0; i < 5; i++ {
			fmt.Println("hello go")
		}
	}()
	for i := 0; i < 2; i++ {
		// 让出时间片,先让别的协程执行,它执行完,再回来执行此协程
		runtime.Gosched()
		fmt.Println("hello")
	}
}
# 输出
hello go
hello go
hello go
hello go
hello go
hello
hello

2、通道(channel)

chan 是Go语言里面的一个关键字,是channel 的简写,翻译为中文就是通道。goroutine是Go语言里面的并发

执行体,通道是goroutine之间通信和同步的重要组件。Go的哲学是不要通过共享内存来通信,而是通过通信来

共享内存,通道是Go通过通信来共享内存的载体。

通道是有类型的,可以简单地把它理解为有类型的管道。声明一个简单的通道语句是 chan dataType,但是简单

声明一个通道变量没有任何意义,a 并没有初始化,其值是 nil。Go 语言提供一个内置函数 make 来创建通道。

通道(channel)是用来传递数据的一个数据结构。

//创建一个无缓冲的通道,通道存放元素的类型为 datatype
make(chan datatype)

//创建一个有10个缓冲的通道,通道存放元素的类型为 datatype
make(chan datatype,10)

通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方

向,发送或接收。如果未指定方向,则为双向通道。

// 把v发送到通道ch
ch <- v
// 从ch接收数据并把值赋给v
v := <-ch

声明一个通道很简单,我们使用 chan 关键字即可,通道在使用前必须先创建:

ch := make(chan int)

默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

// 通过两个goroutine来计算数字之和,在goroutine完成计算后,它会计算两个结果的和
package main

import "fmt"

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	// 把sum发送到通道c
	c <- sum
}

func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	go sum(s[:len(s)/2], c)
	go sum(s[len(s)/2:], c)
	// 从通道c中接收
	x, y := <-c, <-c
	// -5 17 12
	fmt.Println(x, y, x+y)
}

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲

区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发

送数据了。

如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值

被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之

前会一直阻塞。

通道分为无缓冲的通道和有缓冲的通道,Go提供内置函数 len 和 cap,无缓冲的通道的len和cap都是0,有缓冲的

通道的len代表没有被读取的元素数,cap代表整个通道的容量。无缓冲的通道既可以用于通信,也可以用于两个

goroutine的同步,有缓冲的通道主要用于通信。

上节示例代码中,为避免 main goroutine 过早退出,特意 sleep 一段时间。有了通道后,可以使用无缓冲的通

道来实现 goroutines 之间的同步等待。

package main

import (
	"runtime"
)

func main() {
	// 结果
	// NumGoroutine= 2
	// 49995000
	c := make(chan struct{})
	go func(i chan struct{}) {
		sum := 0
		for i := 0; i < 10000; i++ {
			sum += i
		}
		println(sum)
		//写通道
		c <- struct{}{}
	}(c)
	// NumGoroutine可以返回当前程序的goroutine数目
	println("NumGoroutine=", runtime.NumGoroutine())
	// 读通道c,通过通道进行同步等待
	<-c
}

goroutine 运行结束后退出,写到缓冲通道中的数据不会消失,它可以缓冲和适配两个 goroutine 处理速率不一

致的情况,缓冲通道和消息队列类似,有削峰和增大吞吐量的功能。

package main

import (
	"runtime"
)

func main() {
	c := make(chan struct{})
	ci := make(chan int, 100)
	go func(i chan struct{}, j chan int) {
		for i := 0; i < 10; i++ {
			ci <- i
		}
		// 不关闭channel的话,range的读取会死锁
		close(ci)
		//写通道
		c <- struct{}{}
	}(c, ci)
	// NumGoroutine可以返回当前程序的goroutine数目
	println("NumGoroutine=", runtime.NumGoroutine())
	// 读通道c,通过通道进行同步等待
	<-c
	// 此时ci通道已经关闭,匿名函数启动的goroutine已经退出
	println("NumGoroutine=", runtime.NumGoroutine())
	// 但是通道ci还是可以继续读取
	for v := range ci {
		println(v)
	}
}
# 输出
NumGoroutine= 2
NumGoroutine= 2
0
1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
	// 这里我们定义了一个可以存储整数类型的带缓冲通道
	// 缓冲区大小为2
	ch := make(chan int, 2)
	// 因为ch是带缓冲的通道,我们可以同时发送两个数据
	// 而不用立刻需要去同步读取数据
	ch <- 1
	ch <- 2
	// 获取这两个数据
	// 1
	fmt.Println(<-ch)
	// 2
	fmt.Println(<-ch)
}

操作不同状态的 chan 会引发三种行为。

2.1 panic

(1)、向已经关闭的通道写数据会导致 panic。最佳实践是由写入者关闭通道,能最大程度地避免向已经关闭的通

道写数据而导致的 panic。

(2)、重复关闭的通道会导致 panic。

2.2 阻塞

(1)、向未初始化的通道写数据或读数据都会导致当前 goroutine 的永久阻塞。

(2)、向缓冲区已满的通道写入数据会导致 goroutine 阻塞。

(3)、通道中没有数据,读取该通道会导致 goroutine 阻塞。

2.3 非阻塞

(1)、读取已经关闭的通道不会引发阻塞,而是立即返回通道元素类型的零值,可以使用comma,ok语法判断通道

是否已经关闭。

(2)、向有缓冲且没有满的通道读/写不会引发阻塞。

3、Go 遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:

v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	// range函数遍历每个从通道接收到的数据,因为c在发送完10个
	// 数据之后就关闭了通道,所以这里我们range函数在接收到10个数据
	// 之后就结束了。如果上面的c通道不关闭,那么range函数就不
	// 会结束,从而在接收第11个数据的时候就阻塞了。
	/*
		0
		1
		1
		2
		3
		5
		8
		13
		21
		34
	*/
	for i := range c {
		fmt.Println(i)
	}
}

goroutine 是 golang 中在语言级别实现的轻量级线程,仅仅利用 go 就能立刻起一个新线程。多线程会引入线程

之间的同步问题,在 golang 中可以使用 channel 作为同步的工具。

通过 channel 可以实现两个 goroutine 之间的通信。

创建一个 channel, make(chan TYPE {, NUM}),TYPE 指的是 channel 中传输的数据类型,第二个参数是可选

的,指的是 channel 的容量大小。

向 channel 传入数据, CHAN <- DATA , CHAN 指的是目的 channel 即收集数据的一方, DATA 则是要传的数

据。

从 channel 读取数据, DATA := <-CHAN ,和向 channel 传入数据相反,在数据输送箭头的右侧的是 channel,

形象地展现了数据从隧道流出到变量里。

// sleep时间设置长一点,150毫秒,看看会发生什么
package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s, (i+1)*100)
	}
}

func say2(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(150 * time.Millisecond)
		fmt.Println(s, (i+1)*150)
	}
}

func main() {
	go say2("world")
	say("hello")
}

输出结果:

# 输出
hello 100
world 150
hello 200
hello 300
world 300
hello 400
world 450
hello 500

问题来了,say2 只执行了 3 次,而不是设想的 5 次,为什么呢?

原来,在 goroutine 还没来得及跑完 5 次的时候,主函数已经退出了。

我们要想办法阻止主函数的结束,要等待 goroutine 执行完成之后,再退出主函数:

package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Println(s, (i+1)*100)
	}
}

func say2(s string, ch chan int) {
	for i := 0; i < 5; i++ {
		time.Sleep(150 * time.Millisecond)
		fmt.Println(s, (i+1)*150)
	}
	ch <- 0
	close(ch)
}

func main() {
	ch := make(chan int)
	go say2("world", ch)
	say("hello")
	fmt.Println(<-ch)
}

我们引入一个信道,默认的,信道的存消息和取消息都是阻塞的,在 goroutine 中执行完成后给信道一个值 0,则

主函数会一直等待信道中的值,一旦信道有值,主函数才会结束。

# 输出
hello 100
world 150
hello 200
hello 300
world 300
hello 400
world 450
hello 500
world 600
world 750
0

更好的展示边入边出概念:

package main

import (
	"fmt"
	"time"
)

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	for v := range c {
		fmt.Println("out:", time.Now())
		fmt.Println(v)
	}
}

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		fmt.Println("in:", time.Now())
		time.Sleep(100)
		x, y = y, x+y
	}
	close(c)
}
# 输出
in: 2023-06-06 21:14:42.9554962 +0800 CST m=+0.001620501
out: 2023-06-06 21:14:42.9554962 +0800 CST m=+0.001620501
0
in: 2023-06-06 21:14:42.9722573 +0800 CST m=+0.018381601
out: 2023-06-06 21:14:42.9722573 +0800 CST m=+0.018381601
1
in: 2023-06-06 21:14:42.9727771 +0800 CST m=+0.018901401
out: 2023-06-06 21:14:42.9727771 +0800 CST m=+0.018901401
1
in: 2023-06-06 21:14:42.9733159 +0800 CST m=+0.019440201
out: 2023-06-06 21:14:42.9733159 +0800 CST m=+0.019440201
2
in: 2023-06-06 21:14:42.9927205 +0800 CST m=+0.038844801
out: 2023-06-06 21:14:42.9927205 +0800 CST m=+0.038844801
3
in: 2023-06-06 21:14:42.9941545 +0800 CST m=+0.040278801
out: 2023-06-06 21:14:42.9941545 +0800 CST m=+0.040278801
5
in: 2023-06-06 21:14:42.9947202 +0800 CST m=+0.040844501
in: 2023-06-06 21:14:42.9952277 +0800 CST m=+0.041352001
out: 2023-06-06 21:14:42.9947202 +0800 CST m=+0.040844501
8
out: 2023-06-06 21:14:42.9968645 +0800 CST m=+0.042988801
13
in: 2023-06-06 21:14:42.9968645 +0800 CST m=+0.042988801
out: 2023-06-06 21:14:42.9968645 +0800 CST m=+0.042988801
21
in: 2023-06-06 21:14:42.9974584 +0800 CST m=+0.043582701
out: 2023-06-06 21:14:42.9974584 +0800 CST m=+0.043582701
34

关闭通道并不会丢失里面的数据,只是让读取通道数据的时候不会读完之后一直阻塞等待新数据写入。

Channel 是可以控制读写权限的具体如下:

go func(c chan int) { //读写均可的channel c }
go func(c <- chan int) { //只读的Channel }
go func(c chan <- int) {  //只写的Channel } 

形象说明一下无缓冲和有缓冲的区别:

无缓冲是同步的,例如 make(chan int),就是一个送信人去你家门口送信,你不在家他不走,你一定要接下

信,他才会走,无缓冲保证信能到你手上。

有缓冲是异步的,例如 make(chan int, 1),就是一个送信人去你家仍到你家的信箱,转身就走,除非你的信箱

满了,他必须等信箱空下来,有缓冲的保证信能进你家的邮箱。

实例1:

// 通道为1
package main

import (
	"fmt"
	"time"
)

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	fmt.Printf("sum:")
	fmt.Printf("%#v\n", sum)
	c <- sum // 把 sum 发送到通道 c
	fmt.Println("after channel pro")
}

// 通道不带缓冲,表示是同步的,只能向通道 c 发送一个数据,只要这个数据没被接收然后所有的发送就被阻塞
func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	fmt.Println("go [0,3]")
	go sum(s[:len(s)/2], c) //a
	//这里开启一个新的运行期线程,这个是需要时间的,本程序继续往下走
	fmt.Println("go [3,6]")
	go sum(s[len(s)/2:], c) //b
	fmt.Println("go2 [0,3]")
	go sum(s[:len(s)/2], c) //c
	fmt.Println("go2 [3,6]")
	go sum(s[len(s)/2:], c) //d
	/*
	   a b c d和main一起争夺cpu的,他们的执行顺序完全无序,甚至里面不同的语句都相互穿插
	   但无缓冲的等待是同步的,所以接下来a b c d都会执行,一直执行到c <- sum后,开始同步阻塞
	   因此after channel pro是打印不出来的, 等要打印after channel pro的时候,main就结束了
	*/
	fmt.Println("go3 start waiting...")
	time.Sleep(1000 * time.Millisecond)
	fmt.Println("go3 waited 1000 ms")
	//因为a b c d都在管道门口等着,这里度一个,a b c d就继续一个,这个结果的顺序可能是acbd
	aa := <-c
	bb := <-c
	fmt.Println(aa)
	fmt.Println(bb)
	x, y := <-c, <-c
	fmt.Println(x, y, x+y)
}
# 输出
go [0,3]
go [3,6]
go2 [0,3]
go2 [3,6]
go3 start waiting...
sum:sum:17
sum:17
sum:-5
-5
go3 waited 1000 ms
17
17
-5 -5 -10

示例2:

// 通道为2
package main

import (
	"fmt"
	"time"
)

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	fmt.Printf("sum:%#v\n", sum)
	c <- sum // 把 sum 发送到通道 c
	fmt.Println("after channel pro")
}

// 通道不带缓冲,表示是同步的,只能向通道 c 发送一个数据,只要这个数据没被接收然后所有的发送就被阻塞
func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	// 修改成带缓冲的
	c := make(chan int, 2)
	fmt.Println("go [0,3]")
	go sum(s[:len(s)/2], c) //a
	//这里开启一个新的运行期线程,这个是需要时间的,本程序继续往下走
	fmt.Println("go [3,6]")
	go sum(s[len(s)/2:], c) //b
	fmt.Println("go2 [0,3]")
	go sum(s[:len(s)/2], c) //c
	fmt.Println("go2 [3,6]")
	go sum(s[len(s)/2:], c) //d
	/*
	   a b c d和main一起争夺cpu的,他们的执行顺序完全无序,甚至里面不同的语句都相互穿插
	   但无缓冲的等待是同步的,所以接下来a b c d都会执行,一直执行到c <- sum后,开始同步阻塞
	   因此after channel pro是打印不出来的, 等要打印after channel pro的时候,main就结束了
	*/
	fmt.Println("go3 start waiting...")
	time.Sleep(1000 * time.Millisecond)
	fmt.Println("go3 waited 1000 ms")
	//因为a b c d都在管道门口等着,这里度一个,a b c d就继续一个,这个结果的顺序可能是acbd
	aa := <-c
	bb := <-c
	fmt.Println(aa)
	fmt.Println(bb)
	x, y := <-c, <-c
	fmt.Println(x, y, x+y)
}
# 输出
go [0,3]
go [3,6]
go2 [0,3]
sum:17
sum:-5
after channel pro
sum:17
after channel pro
go2 [3,6]
go3 start waiting...
sum:-5

go3 waited 1000 ms
17
-5
17 -5 12

实例3:

// 通道为4
package main

import (
	"fmt"
	"time"
)

func sum(s []int, c chan int) {
	sum := 0
	for _, v := range s {
		sum += v
	}
	fmt.Printf("sum:%#v\n", sum)
	c <- sum // 把 sum 发送到通道 c
	fmt.Println("after channel pro")
}

// 通道不带缓冲,表示是同步的,只能向通道 c 发送一个数据,只要这个数据没被接收然后所有的发送就被阻塞
func main() {
	s := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int, 4)
	fmt.Println("go [0,3]")
	go sum(s[:len(s)/2], c) //a
	//这里开启一个新的运行期线程,这个是需要时间的,本程序继续往下走
	fmt.Println("go [3,6]")
	go sum(s[len(s)/2:], c) //b
	fmt.Println("go2 [0,3]")
	go sum(s[:len(s)/2], c) //c
	fmt.Println("go2 [3,6]")
	go sum(s[len(s)/2:], c) //d
	/*
	   a b c d和main一起争夺cpu的,他们的执行顺序完全无序,甚至里面不同的语句都相互穿插
	   但无缓冲的等待是同步的,所以接下来a b c d都会执行,一直执行到c <- sum后,开始同步阻塞
	   因此after channel pro是打印不出来的, 等要打印after channel pro的时候,main就结束了
	*/
	fmt.Println("go3 start waiting...")
	time.Sleep(1000 * time.Millisecond)
	fmt.Println("go3 waited 1000 ms")
	//因为a b c d都在管道门口等着,这里度一个,a b c d就继续一个,这个结果的顺序可能是acbd
	aa := <-c
	bb := <-c
	fmt.Println(aa)
	fmt.Println(bb)
	x, y := <-c, <-c
	fmt.Println(x, y, x+y)
}
# 输出
go [0,3]
go [3,6]
go2 [0,3]
go2 [3,6]
go3 start waiting...
sum:17
after channel pro
sum:-5
sum:17
after channel pro
sum:-5
after channel pro
after channel pro

go3 waited 1000 ms
17
-5
17 -5 12

可以看到 after channel pro 没有被阻塞了。

package main

import "fmt"

func main() {
	ch := make(chan int, 2)
	ch <- 1
	a := <-ch
	ch <- 2
	ch <- 3
	// 2
	fmt.Println(<-ch)
	// 3
	fmt.Println(<-ch)
	// 1
	fmt.Println(a)
}

通道遵循先进先出原则。

不带缓冲区的通道在向通道发送值时,必须及时接收,且必须一次接收完成。

而带缓冲区的通道则会以缓冲区满而阻塞,直到先塞发送到通道的值被从通道中接收才可以继续往通道传值。就像

往水管里推小钢珠一样,如果钢珠塞满没有从另一头放出,那么这一头就没法再往里塞,是一个道理。例如上面的

例子,最多只能让同时在通道中停放2个值,想多传值,就需要把前面的值提前从通道中接收出去。

// Go 遍历通道与关闭通道的例子这样改一下,会更清楚
package main

import (
	"fmt"
	"time"
)

func fibonacci(n int, c chan int) {
	x, y := 0, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
		time.Sleep(1000 * time.Millisecond)
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
	// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
	// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
	// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
	// 会结束,从而在接收第 11 个数据的时候就阻塞了。
	/*
		0
		1
		1
		2
		3
		5
		8
		13
		21
		34
	*/
	for i := range c {
		fmt.Println(i)
	}
}
// 带缓冲区的 channel 实现异步存取
package main

import (
	"fmt"
	"time"
)

func put(c chan int) {
	for i := 0; i < 10; i++ {
		c <- i
		time.Sleep(100 * time.Millisecond)
		fmt.Println("->放入", i)
	}
	fmt.Println("=所有的都放进去了!关闭缓冲区,但是里面的数据不会丢失,还能取出。")
	close(c)
}

func main() {
	ch := make(chan int, 5)
	go put(ch)
	for {
		time.Sleep(1000 * time.Millisecond)
		data, ok := <-ch
		if ok == true {
			fmt.Println("<-取出", data)
		} else {
			break
		}
	}
}
# 输出
->放入 0
->放入 1
->放入 2
->放入 3
->放入 4
<-取出 0
->放入 5
<-取出 1
->放入 6
<-取出 2
->放入 7
<-取出 3
->放入 8
<-取出 4
->放入 9
=所有的都放进去了!关闭缓冲区,但是里面的数据不会丢失,还能取出。
<-取出 5
<-取出 6
<-取出 7
<-取出 8
<-取出 9

放的速度较快,先放满了 4 个,阻塞住;

取的速度较慢,放了4个才开始取,由于缓冲区已经满了,所以取出一个之后,才能再次放入;

放完了之后虽然缓冲区关闭了,但是缓冲区的内容还保留,所以还能继续取出;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值