协程(Goroutines)在Go语言中,每一个并发的执行单元叫作一个goroutine 。,我们只需要通过 go 关键字来开启 goroutine 即可 。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的 。
goroutine 语法格式:
go 函数名( 参数列表 )
示例:
package mainimport "fmt"func f(from string) { for i := 0; i < 3; i++ {fmt.Println(from, ":", i) }}func main() { // 假设我们有一个函数叫做 f(s) 。// 我们使用一般的方式调并同时运行 。f("direct") // 使用 go f(s) 在一个 Go 协程中调用这个函数 。// 这个新的 Go 协程将会并行的执行这个函数调用 。go f("goroutine") // 你也可以为匿名函数启动一个 Go 协程 。go func(msg string) {fmt.Println(msg) }("going") // 现在这两个 Go 协程在独立的 Go 协程中异步的运行,所以我们需要等它们执行结束 。// 这里的 Scanln 代码需要我们在程序退出前按下任意键结束 。var input string fmt.Scanln(&input) fmt.Println("done") // 当我们运行这个程序时,将首先看到阻塞式调用的输出,然后是两个 Go 协程的交替输出 。// 这种交替的情况表示 Go 运行时是以异步的方式运行协程的 。}
通道(channel)如果说goroutine是Go语言程序的并发体的话,那么channels则是它们之间的通信机制 。通道(channel)是用来传递数据的一个数据结构 。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯 。操作符 <- 用于指定通道的方向,发送或接收 。如果未指定方向,则为双向通道 。
ch <- v// 把 v 发送到通道 chv := <-ch// 从 ch 接收数据// 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
示例:
package mainimport ( "fmt")// 通道 是连接多个 Go 协程的管道 。// 你可以从一个 Go 协程将值发送到通道,然后在别的 Go 协程中接收 。func main() { // 使用 make(chan val-type) 创建一个新的通道 。// 通道类型就是他们需要传递值的类型 。messages := make(chan string) // 使用 channel <- 语法 发送 一个新的值到通道中 。// 这里我们在一个新的 Go 协程中发送 "ping" 到上面创建的messages 通道中 。go func() {messages <- "ping" }() // 使用 <-channel 语法从通道中 接收 一个值 。// 这里将接收我们在上面发送的 "ping" 消息并打印出来 。msg := <-messages fmt.Println(msg) // 我们运行程序时,通过通道,消息 "ping" 成功的从一个 Go 协程传到另一个中 。// 默认发送和接收操作是阻塞的,直到发送方和接收方都准备完毕 。// 这个特性允许我们,不使用任何其它的同步操作,来在程序结尾等待消息 "ping" 。}
通道缓冲区通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据 。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了 。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值 。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值 。接收方在有值可以接收之前会一直阻塞 。
示例:
package mainimport "fmt"// 默认通道是 无缓冲 的,这意味着只有在对应的接收(<- chan)通道准备好接收时,才允许进行发送(chan <-) 。// 可缓存通道允许在没有对应接收方的情况下,缓存限定数量的值 。func main() { // 这里我们 make 了一个通道,最多允许缓存 2 个值 。messages := make(chan string, 2) // 因为这个通道是有缓冲区的,即使没有一个对应的并发接收方,我们仍然可以发送这些值 。messages <- "buffered" messages <- "channel" // 然后我们可以像前面一样接收这两个值 。fmt.Println(<-messages) fmt.Println(<-messages)}
同步实现我们可以通过channel实现同步,如下:
package mainimport "fmt"import "time"// 我们可以使用通道来同步 Go 协程间的执行状态 。// 这里是一个使用阻塞的接受方式来等待一个 Go 协程的运行结束 。func worker(done chan bool) { // 这是一个我们将要在 Go 协程中运行的函数 。// done 通道将被用于通知其他 Go 协程这个函数已经工作完毕 。fmt.Print("working...") time.Sleep(time.Second) fmt.Println("done") // 发送一个值来通知我们已经完工啦 。done <- true}func main() { // 运行一个 worker Go协程,并给予用于通知的通道 。done := make(chan bool, 1) go worker(done) // 程序将在接收到通道中 worker 发出的通知前一直阻塞 。<-done // 如果你把 <- done 这行代码从程序中移除,程序甚至会在 worker还没开始运行时就结束了 。}
推荐阅读
- 阿里+腾讯资深架构师方案-高并发系统下的服务治理
- 华为|华为自研编程语言:下半年见!
- 如何自己编程做游戏 命令提示符大全
- 女大学生|大学女生废话编程爆火!懂不懂编程的看完都拴Q了
- 机器人|萤石发布儿童陪护机器人萤宝RK2遥控编程版:无需识字 3岁就能学编程
- golang编程踩坑记录
- Kali与编程:如何安装KALI LINUX中文拼音输入法?
- 骨折后并发症的食疗方
- 长期卧床的四大并发症
- 结核性腹膜炎最常见的并发症是什么