在Go里,每一个并发执行的活动称为goroutine
。 如果你是一名Java程序员,可以把goroutine
比作为线程,但是goroutine
和线程在数量上有很大的差别,原因在于Go语言引入了协程的概念,协程相比于线程是一种用户态的线程,协程更加轻量,实用更加经济,因此同样的服务器可以开销的协程数量要比线程多很多。
goroutine和协程的区别:
线程和goroutine的区别:
简单的示例代码如下:在Go里,每一个并发执行的活动称为goroutine
。
简单的示例代码如下:
f() //调用f();等它返回go f() // 新建一个调用分()的goroutine,不用等待
在下面的例子中,主goroutine
计算第45个斐波那契数。因为它使用非常低效的递归算法,因此需要大量的时间来执行,在此期间我们提供一个可见的提示,显示一个字符串”spinner“来指示程序依然在运行。
package main import ( "fmt" "time") func spinner(delay time.Duration) { for { for _, r := range `-\|/` { fmt.Printf("\r%c", r) time.Sleep(delay) } }} func fib(x int) int { if x < 2 { return x } return fib(x-1) + fib(x-2)} func main() { go spinner(100 * time.Microsecond) const n = 45 fibN := fib(n) // slow fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) }
若干秒后,fib(45)返回结果,如下图所示:
然后main
函数返回,所有的goroutine
都暴力地直接终结,然后程序退出。
如果说goroutine
是Go程序并发的执行体,通道就是它们之间的连接。通道是可以让一个goroutine
发送特定值到另一个goroutine
的通信机制。每一个通道是一个具体类型的导管,叫做通道的元素类型。一个有int
类型元素的通道写为chan int
。
使用内置的make
函数来创建一个通道:
ch := make(chan int) // ch的类型是 chan int
像map
一样,通道是一个使用make
创建的数据结构的引用。当复制或者作为参数传递到一个函数时,复制的是引用,这样调用者和被调用者都引用同一份数据结构。和其他引用类型一样,通道的零值是nil
。
通道有两个主要的操作:发送和接收,这两者统称为通信。send
语句从一个goroutine
传输一个值到另一个在执行接收表达式的goroutine
。两个操作都使用<-
操作符书写。发送语句中,通道和值分别在<-
的左右两边。在接收表达式中,<-
放在通道操作数的前面。
具体书写格式如下:
ch <- x //发送语句x = <- ch // 赋值语句中的接收表达式<- ch // 接收语句,丢弃结果
通道支持第三个操作:关闭,它设置一个标志位来指示值当前已经发送完毕,这个通道后面没有值了,关闭后的发送操作将导致宕机。在一个已经关闭的通道上进行接收操作,将获取所有已经发送的值,直到通道为空,这是任何接收操作会立即完成,同时获取到一个通道元素类型对应的零值。
调用内置的close
函数来关闭通道:
close(ch)
使用简单的make
调用创建的通道叫做无缓冲通道,但make
还可以接受第二个可选参数,一个表示通道容量的整数。如果容量是0,make
创建一个无缓冲的通道。
ch = make(chan int) // 无缓冲通道ch = make(chan int, 0) // 无缓冲通道ch = make(chan int, 3) // 容量为3的缓冲通道
无缓冲通道上的发送操作将会阻塞,直到另一个goroutine
在对应的通道上执行接收操作,这时值传送完成,两个goroutine
都可以继续执行。相反,如果接收操作先执行,接收方goroutine
将阻塞,直到另一个goroutine
在同一个通道发送一个值。
使用无缓冲通道进行通信导致发送和接收goroutine
同步化。因此,无缓冲通道也称为同步通道。当一个值在无缓冲通道上传递时,接收值后发送方goroutine
才被再次唤醒。
package mainimport "fmt"func main(){ ch:=make(chan int) //这里就是创建了一个channel,这是无缓冲管道注意 go func(){ //创建子go程 for i:=0;i<=6;i++{ ch<-i //循环写入管道 fmt.Println("写入",i) } }() for i:=0;i<6;i++{ //主go程 num:=<-ch //循环读出管道 fmt.Println("读出",num) }}
缓冲通道有一个元素队列,队列的最大长度在创建的时候通过make
的容量参数来设置。如下代码创建了一个带有10个字符串的缓冲通道:
ch = make(chan string,10)
缓冲通道上的发送操作在对列的尾部插入一个元素,接收操作从队列的头部移除一个元素。如果通道满了,发送操作会阻塞所在的goroutine
直到另一个goroutine
对它进行接收操作来腾出可用的空间。反过来,如果通道是空的,执行接收操作的goroutine
阻塞,直到另一个goroutine
在通道上发送数据。
现在,我们可以在通道上无阻塞的发送三个值,但是在发送第四个值的时候就会阻塞。
package main func main() { ch := make(chan string, 3) ch <- "A" ch <- "B" ch <- "C" ch <- "D"}
在我们向管道塞入第四个值的时候,程序爆出了死锁的异常,如下图:
但是当我们在执行第四次向通道塞值的时候,从通道取出一个值,就可以安全的进行第四次塞值了,并且成功的打印出了队列的第一个元素A,如下图:
通道可以用来连接goroutine
,这样一个具体的输出是另一个的输入。管道一般是由三个goroutine
组成,使用两个通道连接起来。
如下代码所示:
package main import "fmt" func main() { naturals := make(chan int) squares := make(chan int) // counter go func() { for x := 0; x< 100; x++ { naturals <- x } close(naturals) }() // squares go func() { for { x,ok := <-naturals if !ok { break // 通道关闭并且读完 } squares <- x * x } close(squares) }() // printer(在主goroutine中) for x := range squares{ fmt.Println(x) } }
结束时,关闭每一个通道不是必需的,只有在通知接收方goroutine
所有数据都发送完毕的时候才需要关闭通道。通道也是可以通过垃圾回收器根据它是否可以访问来决定是否回收它,而不是根据它是否关闭。
不要将这个close
操作和对于文件的close
操作混淆。当结束的时候对每一个文件调用Close
方法是非常重要的。
Go也提供了单向通道类型,仅仅导出发送或者接收操作。类型chan <- int
是一个只能发送的通道,允许接收但是不能发送。(<-
操作符相对于chan
关键字的位置是一个帮助记忆的点)。
package main import "fmt" // 单向输出通道 chan<-func counter(out chan<- int) { for x := 0; x < 100; x++ { out <- x }} // 单向输出通道 chan<-func squarer(out chan<- int, in <-chan int) { for v := range in { out <- v * v }} // 单向输入通道 <-chanfunc printer(in <-chan int) { for v := range in { fmt.Println(v) }} func main() { naturals := make(chan int) squares := make(chan int) go counter(naturals) go squarer(squares, naturals) printer(squares)}
为了演示并行循环,我们考虑生成一批全尺寸图像的缩略图,代码如下:
package main import ( "gopl.io/ch8/thumbnail" "io/ioutil" "log") func makeThumbnails(filenames []string) { for _, f := range filenames { if _, err := thumbnail.ImageFile(f); err != nil { log.Println(err) } } }func main() { rootPath := "W:\\Google_Download\\壁纸\\动漫壁纸\\" //var rootPath string = "W:\\Google_Download\\壁纸\\动漫壁纸\\" files, _ := ioutil.ReadDir(rootPath) var fileNames []string /* 第一种读取文件列表的方法 */ for _, f := range files { //fmt.Println(f.Name()) fileNames = append(fileNames, rootPath+f.Name()) } makeThumbnails(fileNames)}
其中需要导入的gopl.io/ch8/thumbnail
包,从下面的网站下载:
GitHub - adonovan/gopl.io: Example programs from "The Go Programming Language"](https://github.com/adonovan/gopl.io/)
生成后的结果如图所示,都是我喜欢的动漫图片,如果也喜欢,私信我给你发哈~
以上就是Go语言关于goroutine
和通道的内容,关于goroutine
和通道其实还有很多可以深挖的东西,我们后面会继续学习。希望这篇文章可以帮助到你~
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。