# golang并发一
在go里,每一个并发执行的活动称为goroutine。go有两种并发编程风格,通信顺序进程模式(基于channel),共享内存。
在go里面创建一个goroutine特别简单,只需在函数前面添加关键字go即可
```go
func f() {
//TODO ADD CODE HERE
}
func main() {
go f()
}
```
go f()即创建了一个goroutine,并且并发执行函数f
## 通信顺序进程模式
### 通道
通道是goroutine之间的连接,可以让一个goroutine将值发送给另一个goroutine。每个通道都有一个具体的类型,一个int型的通道写作chan int。
通道有三个操作:发送,接收,关闭(不常用)。
缓冲通道有一个缓冲队列,接收操作从队列的头部移除一个元素,发送操作在队列的尾部插入一个元素,如果队列已满,则发送操作阻塞,队列为空,则接受操作为空。
当通道容量为0时,发送操作会阻塞,直到另一个goroutine进行接收操作,这时值传送完成,两个goroutine继续工作。接收操作同理。
代码示例
- 创建通道
```go
channel := make(chan int)
```
- 创建缓冲通道
```go
channel := make(chan struct{},3)
```
- 向通道发送数据
```go
channel <- 1
```
- 从通道接收数据
```go
_ = <- channel
```
- 关闭通道
```go
close(channel)
```
如果一个通道关闭之后,任何发送操作都会引起宕机,接收操作马上返回通道的元素类型的0值,不阻塞
我们可能需要限制某个函数只可以进行发送操作,某个函数只可以进行接收操作,这可以通过在参数类型中声明chan <- int 表示这可以进行发送操作,类型<- chan int表示只可以进行接收操作。
一下代码我们实现一个生产者和消费者示例:
```go
func main() {
channel := make(chan int, 10)
for i := 0; i < 10; i++ {
go procedur(channel)
}
for i := 0; i < 12; i++ {
go consumer(channel, i)
}
for {
time.Sleep(time.Second)
}
}
//生产者 只可以进行发送操作
func procedur(channel chan<- int) {
for i := 0; i < 101; i++ {
channel <- i
i %= 100
time.Sleep(time.Millisecond * 100)
}
}
//生产者 只可以进行接收操作
func consumer(channel <-chan int, id int) {
for {
v := <-channel
fmt.Println(id, v)
time.Sleep(time.Millisecond * 200)
}
}
```
模仿了十个生产者和十二个消费者的情况
### select 多路复用
select 语句类似switch 语句,有一系列情况和一个可选的默认分支,每一个情况指定一次通信和关联的代码块。
以下势力为随机生成数字以及输出的示例
```go
channel := make(chan int, 10)
for i := 0; i < 100; i++ {
select {
case v := <-channel:
fmt.Println(v)
case channel <- i:
}
}
```
需要注意的是,如果有多个情况同时满足,select随机选择一个进行执行。
## 共享内存
### 互斥锁 sync.Mutex
互斥锁模式应用非常广泛,所以sync包有一个单独的Mutex类型来支持这种模式。它的Lock方法用于获取锁,Unlock方法用于释放锁。如果正在获取的锁被已经被其他goroutine获取到,那么操作会一直阻塞,知道其他gorutine释放锁。所以一个共享变量可以通过一个互斥锁来进行保护。需要注意的是go语言的互斥量是不可再入的,即当前goroutine获取到锁后,不可再次获取该锁。
下面实现一个简单的账户余额充值提现代码。
```go
var (
balance float64 = 0
mutex = sync.Mutex{}
)
//充值
func Recharge(value float64) {
mutex.Lock()
defer mutex.Unlock()
time.Sleep(time.Millisecond * 10)
balance += value
}
//提现
func WithDraw(value float64) bool {
mutex.Lock()
defer mutex.Unlock()
time.Sleep(time.Millisecond * 10)
//余额小于需要提现的金额,不可以提现
if balance < value {
return false
}
balance -= value
return true
}
//查看余额
func Balance()float64 {
mutex.Lock()
defer mutex.Unlock()
return balance
}
```
充值和提现加入Sleep功能是为了增加协程在执行中对资源的抢占。
### 读写互斥锁sync.RWMutex
在特殊环境下,我们可能需要读操作可以并发的执行,但是同一时间只可以有一个写操作,我们通过sync.RWMutex来实现。
```go
var (
balance float64 = 0
mutex = sync.RWMutex{}
)
//查看余额
func Balance() float64 {
mutex.RLock()
defer mutex.RUnlock()
return balance
}
```
通过RLock和RUnlock分别获取和释放一个度锁。RLock只能用在临界区域内对共享变量无写操作的情形。
有疑问加站长微信联系(非本文作者)