创建一个协程非常简单

Golang中的并发是函数相互独立运行的能力。

Goroutines是并发运行的函数。
Golang提供了Goroutines作为并发处理操作的一种方式。

创建一个协程非常简单,就是在一个任务函数前面添加一个go关键字:

package main

import (
	"fmt"
	"time"
)

func show(msg string) {
	for i := 1; i < 5; i++ {
		fmt.Printf("msg: %v\n", msg)
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
	go show("java")
	// 在main协程中执行,如果它前面也添加go,程序没有输出
	show("golang")
	fmt.Println("end...") // 主函数退出,程序就结束了
}
PS E:\TEXT\test> go run .
msg: golang
msg: java
msg: java
msg: golang
msg: golang
msg: java
msg: golang
msg: java
end...
PS E:\TEXT\test>

查看 go 关键字去掉的运行结果

package main

import (
	"fmt"
	"time"
)

func show(msg string) {
	for i := 1; i < 5; i++ {
		fmt.Printf("msg: %v\n", msg)
		time.Sleep(time.Millisecond * 100)
	}
}

func main() {
	show("java")
	show("golang")        // 在main协程中执行,如果它前面也添加go,程序没有输出
	fmt.Println("end...") // 主函数退出,程序就结束了
}
PS E:\TEXT\test> go run .
msg: java
msg: java
msg: java
msg: java
msg: golang
msg: golang
msg: golang
msg: golang
end...
PS E:\TEXT\test>

通道的机制,用于在goroutine之间共享数据

Go提供了一种称为通道的机制,用于在goroutine之间共享数据。当您作为goroutine执行并发活动时,需要在goroutine之间共享资源或数据,通道充当goroutine之间的管道(管道)并提供一种机制来保证同步交换。

需要在声明通道时指定数据类型。我们可以共享内置、命名、结构和引用类型的值和指针。数据在通道上传递:在任何给定时间只有一个goroutine可以访问数据项:因此按照设计不会发生数据竞争。

根据数据交换的行为,有两种类型的通道:无缓冲通道和缓冲通道。无缓冲通道用于执行goroutine之间的同步通信,而缓冲通道用于执行异步通信。无缓冲通道保证在发送和接收发生的瞬间两个goroutine之间的交换。缓冲通道没有这样的保证。

通道由make函数创建,该函数指定chan关键字和通道的元素类型。

创建无缓冲和缓冲通道的代码块

Unbuffered := make(chan int) // 整型无缓冲通道 
buffered := make(chan int, 10) // 整型有缓冲通道 

使用内置函数 make 创建无缓冲和缓冲通道。
make 的第一个参数需要关键字chan,然后是通道允许交换的数据类型。

<-
goroutine1 := make(chan string, 5) // 字符串缓冲通道 
goroutine1 <- "Australia" // 通过通道发送字符串  

一个包含 5 个值的缓冲区的字符串类型的 goroutine1 通道。
然后我们通过通道发送字符串“Australia”。

从通道接收值的代码块:

data := <- goroutine1 // 从通道接收字符串 
<-

无缓冲通道是同步的定义

在无缓冲通道中,接收到任何值之前没有能力保存它。

在这种类型的通道中,发送和接收 goroutine 在任何发送或接收操作完成之前的同一时刻都准备就绪。

如果两个 goroutine 没有在同一时刻准备好,则通道会让执行其各自发送或接收操作的 goroutine 首先等待。

同步是通道上发送和接收之间交互的基础。
没有另一个就不可能发生。

有缓冲通道是异步的定义

在缓冲通道中,有能力在接收到一个或多个值之前保存它们。

在这种类型的通道中,不要强制 goroutine 在同一时刻准备好执行发送和接收。

当发送和接收阻塞时也有不同的条件。只有当通道中没有要接收的值时,接收才会阻塞。仅当没有可用缓冲区来放置正在发送的值时,发送才会阻塞。

通道的发送和接收特性

  • 对于同一个通道,发送操作之间是互斥的。接收操作之间也是互斥的。
  • 发送操作和接收操作中对元素值的处理都是不可分割的。
  • 发送操作在完全完成之前会被阻塞。接收操作也是如此。
package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 创建int类型通道,只能传入int类型值
var values = make(chan int)

func send(rng *rand.Rand) {
	value := rng.Intn(10)
	fmt.Printf("send: %v\n", value)
	// time.Sleep(time.Second * 5)
	values <- value
}

func main() {
	// 使用时间种子创建随机数生成器
	seed := time.Now().UnixNano()
	rng := rand.New(rand.NewSource(seed))

	// 从通道接收值
	defer close(values)
	go send(rng)
	fmt.Println("wait...")
	value := <-values
	fmt.Printf("receive: %v\n", value)
	fmt.Println("end...")
}
PS E:\TEXT\test> go run .
wait...
send: 5
receive: 5
end...
PS E:\TEXT\test>

WaitGroup实现同步

package main

import (
	"fmt"
	"sync"
)

var wg sync.WaitGroup

func hello(i int) {
	defer wg.Done() // gorontine结束就登记-1
	fmt.Printf("i: %v\n", i)
}

func main() {
	for i := 0; i < 10; i++ {
		wg.Add(1) // 启动一个goroutine就登记+1
		go hello(i)
	}
	wg.Wait() // 等待所有登记的goroutine都结束
}
PS E:\TEXT\test> go run .
i: 9
i: 0
i: 2
i: 5
i: 3
i: 4
i: 7
i: 8
i: 6
i: 1
PS E:\TEXT\test>