Go笔记-goroutine

1.goroutine

特点:

1.有独立的栈空间

2.共享程序堆空间

3.调度由用户控制

4.协程是轻量级线程

goroutine 是一个轻量级的执行单元,相比线程开销更小,完全由 Go 语言负责调度,是 Go 支持并发的核心。开启一个 goroutine 非常简单:

package main
import (
	"fmt"
	"time"
)

func main() {
	go fmt.Println("goroutine message")
	time.Sleep(1) //1
	fmt.Println("main function message")
}

#1 的代码是必须的,这是为了让新开启的 goroutine 有机会得到执行,开启一个 goroutine 之后,后续的代码会继续执行,在上面的例子中后续代码执行完毕程序就终止了,而开启的 goroutine 可能还没开始执行。

如果尝试去掉 #1 处的代码,程序也可能会正常运行,这是因为恰巧开启的 goroutine 只是简单的执行了一次输出,如果 goroutine 中耗时稍长就会导致只能看到主一句 main function message

换句话话说,这里的 time.sleep 提供的是一种调度机制,这也是 Go 中 channel 存在的目的:负责消息传递和调度。

2.channel

“对于closed或nil通道,发送和接收操作都有相应规则:

1.向已关闭通道发送数据,引发panic。
2.从已关闭接收数据,返回已缓冲数据或零值。
3.无论收发,nil通道都会阻塞”

Channel 是 Go 中为 goroutine 提供的一种通信机制,借助于 channel 不同的 goroutine 之间可以相互通信。channel 是有类型的,而且有方向,可以把 channel 类比成 unix 中的 pipe。Go 通过 <- 操作符来实现 channel 的写和读,send value <- 在 channel 右侧,receive value <- 在左侧,receive value 不赋值给任何变量是合法的。

i := make(chan int)//int 类型
s := make(chan string)//字符串类型
r := make(<-chan bool)//只读
w := make(chan<- []int)//只写
<- w //合法的语句

Channel 最重要的作用就是传递消息。

package main
import (
	"fmt"
)

func main() {
	c := make(chan int)
	go func() {
		fmt.Println("goroutine message")
		c <- 1 //1
	}()
	<-c //2
	fmt.Println("main function message")
}

例子中声明了一个 int 类型的 channel,在 goroutine 中在代码 #1 处向 channel 发送了数据 1 ,在 main 中 #2 处等待数据的接收,如果 c 中没有数据,代码的执行将发生阻塞,直到有 goroutine 开始往 c 中 send value。

这是 channel 最简单的用法之一:同步,这种类型的 channel 容量是 0,称之为 unbuffered channel

3.Unbuffered channel

Channel 可以设置容量,表示 channel 允许接收的消息个数,默认的 channel 容量是 0 称为 unbuffered channel ,对 unbuffered channel 执行 操作 value := <-ch 会一直阻塞直到有数据可接收,执行 操作 ch <- value 也会一直阻塞直到有 goroutine 对 channel 开始执行接收,正因为如此在同一个 goroutine 中使用 unbuffered channel 会造成 deadlock。

package main
import (
	"fmt"
)

func main() {
	c := make(chan int)
	c <- 1
	<-c
	fmt.Println("main function message")
}

执行报 fatal error: all goroutines are asleep - deadlock! ,读和写相互等待对方从而导致死锁发生。

来自 www.goinggo.net

4.Buffered channel

如果 channel 的容量不是 0,此类 channel 称之为 buffered channel ,buffered channel 在消息写入个数 未达到容量的上限之前不会阻塞 ,一旦写入消息个数超过上限,下次输入将会阻塞,直到 channel 有位置可以再写入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OG7KyYuv-1584522286495)(http://static.git-star.com/Screen+Shot+2014-02-17+at+8.38.15+AM.jpg)]

package main
import (
	"fmt"
)

func main() {
	c := make(chan int, 3)
	go func() {
		for i := 0; i < 4; i++ {
			c <- i
			fmt.Println("write to c ", i)
		}
	}()

	for i := 0; i < 4; i++ {
		fmt.Println("reading", <-c)
	}
}

输出:

write to c 0
reading 0
write to c 1
reading 1
write to c 2
reading 2
write to c 3
reading 3

根据上文对 buffered channel 的解释,这个例子中 channel c 的容量是 3,在写入消息个数不超过 3 时不会阻塞,输出应该是:

write to c 0
write to c 1
write to c 2
reading 0
reading 1
reading 2
write to c 3
reading 3

问题其实是在 fmt.Println ,一次输出就导致 goroutine 的执行发生了切换(相当于发生了 IO 阻塞),因而即使 c 没有发生阻塞 goroutine 也会让出执行,一起来验证一下这个问题。

package main
import (
	"fmt"
	"strconv"
)

func main() {
	c := make(chan int, 3)
	s := make([]string, 8)
	var num int = 0
	go func() {
		for i := 0; i < 4; i++ {
			c <- i
			num++
			v := "inner=>" + strconv.Itoa(num)
			s = append(s, v)
		}
	}()

	for i := 0; i < 4; i++ {
		<-c
		num++
		v := "outer=>" + strconv.Itoa(num)
		s = append(s, v)
	}

	fmt.Println(s)
}

这里创建了一个 slice 用来保存 c 进行写入和读取时的执行顺序,num 是用来标识执行顺序的,在没有加入 Println 之前,最终 s 是 [inner=>1 inner=>2 inner=>3 inner=>4 outer=>5 outer=>6 outer=>7 outer=>8] ,输出结果表明 c 达到容量上线之后才会发生阻塞。

相反有输出语句的版本结果则不同:

package main
import (
	"fmt"
	"strconv"
)

func main() {
	c := make(chan int, 3)
	s := make([]string, 8)
	var num int = 0
	go func() {
		for i := 0; i < 4; i++ {
			c <- i
			num++
			v := "inner=>" + strconv.Itoa(num)
			s = append(s, v)
			fmt.Println("write to c ", i)
		}
	}()

	for i := 0; i < 4; i++ {
		num++
		v := "outer=>" + strconv.Itoa(num)
		s = append(s, v)
		fmt.Println("reading", <-c)
	}

	fmt.Println(s)
}

[outer=>1 inner=>2 outer=>3 inner=>4 inner=>5 inner=>6 outer=>7 outer=>8] 输出结果能表明两个 goroutine 是交替执行,也就是说 IO 的调用 Println 导致 goroutine 的让出了执行。

5.使用 select 读取多个 channel

Go 提供了 select 语句来处理多个 channel 的消息读取。

package main
import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		for {
			c1 <- "from 1"
			time.Sleep(time.Second * 2)
		}
	}()

	go func() {
		for {
			c2 <- "from 2"
			time.Sleep(time.Second * 2)
		}
	}()

	go func() {
		for {
			select {
			case msg1 := <-c1:
				fmt.Println(msg1)
			case msg2 := <-c2:
				fmt.Println(msg2)
			}
		}
	}()

	var input string
	fmt.Scanln(&input)

}

select 语句可以从多个可读的 channel 中随机选取一个执行,注意是 随机选取。

6.关闭 Channel

Channel 可以被关闭 closechannel 关闭之后仍然可以读取,但是向被关闭的 channel send 会 panic。如果 channel 关闭之前有值写入,关闭之后将依次读取 channel 中的消息,读完完毕之后再次读取将会返回 channel 的类型的 zero value:

package main
import (
	"fmt"
)

func main() {
	c := make(chan int, 3)
	go func() {
		c <- 1
		c <- 2
		c <- 3
		close(c)
	}()

	fmt.Println(<-c)
	fmt.Println(<-c)
	fmt.Println(<-c)
	fmt.Println(<-c)
	fmt.Println(<-c)
	fmt.Println(<-c)
}

输出 1 2 3 0 0 0 ,0 是 int channel c 的 zero value。

package main
import (
	"fmt"
)

func main() {
	c := make(chan int, 3)
	go func() {
		c <- 1
		c <- 2
		c <- 3
		close(c)
	}()

	for i := range c {
		fmt.Println(i)
	}
}

c 可以进行 range 迭代,如果 channel 没有被关闭 range 会一直等待 channel,但是关闭 channel 之后可以隐式的中断 range 的迭代

7.判断 channel 的关闭

Go 提供了 ok 表达式来判断 channel 的关闭状态。

value, ok <- c

如果 channel 是关闭状态,ok 是 false,value 是 channel 的 zero value,否则 ok 是 true 表示 channel 未关闭,value 表示 channel 中的值。

8.单向channel

默认情况下,通道是双向的,也就是,既可以往里面发送数据也可以从里面接收数据.

但是,我们经常见一个通道作为参数进行传递而只希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,
这时候我们可以指定通道的方向.

创建单向channel:

var ch1 chan int		//这是一个正常的channel,不是单向的
var ch2 chan<- float64	//只用于写float64数据
var ch3 <-chan int		//只用于读int数据
* chan<-	表示数据进入管道,要把数据写进管道,对于调用者就是输出.
* <-chan	表示数据从管道出来,对于调用者就是得到管道的数据,当然就是输入

可以将channel隐式转换为单向队列,只收或只发,不能将单向channel转换为双向channel

单向channel特性:

ch := make(chan int) 		//创建一个双向channel

//双向channel隐式转换为单向channel
var WriteCh chan<- int = ch 	//只能写,不能读(输入端可以close)
var ReadCh <-chan int = ch  	//只能读,不能写(接收端不能close)

WriteCh <- 1000
<-ReadCh

接收端和输入端不能相互转换

单向channel的应用:

//此通道只能写,不能读
func producer(out chan<- int) {
	for i := 0; i < 10; i++ {
		fmt.Println("写",i)
		out <- i * i
	}
	close(out)
}

//此通道只能读,不能写
func consumer(in <-chan int) {
	for value := range in {
		fmt.Println("读",value)
	}
}
func main() {
	ch := make(chan int)
	
	go producer(ch)		//生产者,生产数字,写入channel
	
	consumer(ch)		//消费者,从channel读取内容,打印
}
01012349451625673649896481
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值