程序间的并发通信
并发程序间的最大问题是通信,而最常见的并发通信模型是 共享内存 和 消息机制。
1、共享内存(Shared Memory)
- 是指多个并发单位分别保存对同一数据的引用,实现对该数据的共享;
- 多个并发单位在同时访问共享内存时,必须使用互斥锁等相关机制,以保证对共享内存的互斥访问。
2、消息机制(Message Communication Mechanism)
- Go语言中,是以消息机制作为主要通信方法;
- 消息机制规定每个并发单位是自包含的、独立的个体,并且都有自己的变量,这些变量不能在不同的并发单位之间共存。
- 每个并发单位的输入、输出只有一种,就是消息;
- 不同进程间靠消息进行通信,而不会共享内存数据。
不同 goroutine 之间如何通信
1、全局变量的互斥锁
因未对全局变量加锁,所以会出现资源争夺问题,代码会出现错误
解决方案:通过 sync包 加入互斥锁 Mutex
sync包提供了基本的同步的基元,如互斥锁。除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些。
package main
import (
"fmt"
"sync"
"time"
)
//使用goroutine,计算1-20的阶乘,并把各个数的阶乘放入map中
//思路:
//1.编写一个函数,来计算各个数的阶乘,并放入map中
//2.启动多个协程,统计的结果放入到map中
//3.map应该做出一个全局的
var (
myMap = make(map[int]int, 10)
//声明一个全局的互斥锁
lock sync.Mutex
)
func test(n int){
res := 1
for i := 1; i<=n; i++ {
res *= i
}
//这里将res放入到myMap
//加锁
lock.Lock()
myMap[n] = res
//解锁
lock.Unlock()
}
func main(){
//开启多个协程完成任务
for i := 1; i<=20; i++{
go test(i)
}
//休眠5秒钟
time.Sleep(time.Second * 5)
//输出结果
lock.Lock()
for i, v := range myMap {
fmt.Printf("map[%d]=%d\n", i, v)
}
lock.Unlock()
}
Channel
- 通道(channel)是用来传递数据的一个数据结构——队列。
- 通道可用于两个或多个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
- channel 中数据是先进先出(FIFO)
- channel 中数据的发送和接收都是原语操作,不会中断,只会失败。
Channel的声明和初始化
和一般变量声明基本相同,只是在类型前加关键字 “chan”:
var chanName chan ElementType
ElementType:指定Channel所能传递的数据类型
还可以使用 "make()" 函数直接声明和初始化:
ch := make(chan int)
根据数据交换行为,分无缓冲通道和缓冲通道
无缓冲通道: 用于执行goroutine之间的同步通信
缓冲通道: 用于执行异步通信
无缓冲通道保证在发送和接收发生的瞬间执行两个goroutine之间的交换;缓存通道没有这样的保证。
Unbuffered := make(chan int) //整型无缓冲通道
buffered := make(chan int, 10) //整型有缓冲通道
数据的接受和发送
操作符 <- 用于指定通道的方向,发送或接收。
如果未指定方向,则为双向通道。
将一个数据发送(写入)至Channel:
ch <- value
向Channel写入数据通常会导致程序阻塞(Block),直到有其他Goroutine从这个Channel中读取数据。
从Channel中接收(读取)数据:
value := <- ch
也可用以下语法取出数据丢弃:
<- ch
如果Channel之前没有写入数据,那么从Channel中读取数据也会导致阻塞,直到Channel中被写入数据为止。
实例:
package main
import (
"fmt"
"math/rand"
)
func Test(ch chan int) {
ch <- rand.Int() //向 Channel 中写入一个随机数
fmt.Println("Go...")
}
func main() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Test(chs[i]) //启动10个Goroutine
}
for _, ch := range chs {
value := <-ch //阻塞等待退出信号
fmt.Println(value)
}
}
运行结果:
Go...
7188982543361109100
6951295209423991719
4522349878514052167
7843658900962395408
6975911838343108630
8201548578515923750
1820885331502800729
5307814057134748065
5747925000352368897
6425996854107296150
channel 的关闭
使用内置函数 close() 可以关闭 channel,当 channel 关闭后,就不能向 channel 写数据,但仍然可以从该 channel 读取数据。
判断channel是否被关闭,可以在读取channel的时候使用多重返回值的方式:
value, ok := <- ch
如果返回值是 false 则表示channel已被关闭。
channel 的遍历
channel 支持 for-range 的方式进行遍历
注意:
1、在遍历时,如果 channel 没有关闭,则会出现 deadlock 的错误;
2、在遍历时,如果 channel 已经关闭,则会正常遍历数据,遍历完成后会退出遍历。
//放入10个数据到通道中
intChan := make(chan int, 10)
for i := 0; i<100; i++ {
intChan <- i*2
}
//遍历通道channel,前提需要关闭通道
close(intChan)
for v := range intChan {
fmt.Println("v=%d\n",v)
}
单向channel
可将channel指定为单向通道,即只能发送,或只能接收。
只能发送的channel变量的定义:
var chanName <- chan ElementType
只能接收的channel变量的定义:
var chanName chan <- ElementType
在定义了单向channel后,还要对其初始化。
单向channel也可由一个已定义的双向(正常)channe转换而来:
ch := make(chan int)
chRead := <- chan int(ch)
chWrite := chan <- int(ch)
异步channel
对于在goroutine间传输大量数据的应用,可以使用异步通道,从而达到消息队列的效果。
异步channel,就是给channel设定一个Buffer值。
在Buffer 未写满 / 未读完 的情况下,不阻塞 发送 / 接收 操作。
在调用make()函数时,将缓冲区的大小作为第二个参数传入即可,例如:
ch := make(chan int, 1024)