协程
协程基本
协程是以函数的形式进行的
语法
go 函数名()
依赖包
sync中的WaitGroup
var wg sync.WaitGroup
wg.Add()添加一个协程
wg.Done()减少一个协程
wg.Wait()主线程使用,等待协程完成够做后再往下走。
recover异常捕获
在开发中会开启多个协程,一个携程出错会导致整个程序panic而停止,
使用recover捕获异常,可以使一个协程出错panic,而不会导致其他携程也停止运行。
eg:
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func sayhello() {
for i := 0; i < 10; i++ {
fmt.Println("hello")
time.Sleep(time.Second)
}
wg.Done()
}
func test() {
//这里的defer+recover机制不会影响其它协程执行
defer func() {
if err := recover(); err != nil {
fmt.Println("test():", err)
}
}()
var mapint map[int]string
mapint[1] = "goalng" //err没有make,该test携程会出错
wg.Done()
}
func main() {
wg.Add(2)
go sayhello()
go test()
wg.Wait()
}
管道
管道基本
1.管道就是队列
2.多个goroutine操作管道是安全的
3.管道有类型,一个string类型的管道只能存放string类型
4.管道是引用类型,要make分配内存空间后才可以使用
5.管道不同于map,不会自动增长,超过上限会报错,管道内没有数据是再取出也会报错。
6.尾进头出,先进先出
语法
1.初始化
var mychan chan int
var 名字 chan 类型
2.make分配内存
mychan=make(chan int ,10)
mychan=make(chan 类型,容量)
2.存入:
mychan
存入时只能存入相同的类型
注意事项
想存入不同类型,要将管道定义成空接口类型,(任何类型都实现了空接口),但要是结构体的话,要进行类型断言
实例
存入:
catchan:=make(chan interface{},3)
cat:=Cat{
Name:”tom“
Age:=10
}
catchan
取出
newcat:=
fmt.Println(newcat.Name) //这个是错的默认是空接口类型,而接口类型没有字段
//进行类型断言
cat1:=newcat.(Cat)
fmt.Println(cat1.Name) //成功
channel关闭
关闭
使用内置函数close可以关闭channel 关闭之后不能在存入数据了,但是可以读取数据
遍历
遍历时要关闭channel,否则会出现错误
不能用for-i遍历
要用for-range遍历
管道阻塞机制
当管道容量小余要存入的数据,并且只进不读,就会出现管道阻塞deadlock
写入管道和读取管道的频率不一致无所谓
channel细节
1.channel可以声明为只读或只写
eg:
ch:=make(chan ch1:=make(
2.select使用
当不确定什么时候关闭channel时,直接读取的话会造成死锁(deadlock)
使用select则不会会造成阻塞
如果当前select的case中没有满足,则会自动跳到下一个
eg:
func main() {
//使用select可以解决从管道取数据的阻塞问题
intchan := make(chan int, 10)
for i := 0; i < 10; i++ {
intchan <- i
}
//定义一个管道,5个string
stringchan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringchan <- "hello" + fmt.Sprintf("%d", i)
}
//传统的方法在遍历管道是会阻塞而导致阻塞,而导致死锁
//问题在实际开发中,可能我们不确定什么时候关闭管道
//使用select可以解决问题
lable:
for {
select {
case v := <-intchan:
//如果intchan一直没有关闭,不会一直阻塞而导致死锁
//会自动到下一个case进行匹配
fmt.Printf("从intchan 读取了数据%v\n", v)
case v := <-stringchan:
fmt.Printf("从stringchan中取出的数据%v\n", v)
default:
fmt.Println("都没啦")
break lable
}
}
}
锁
go 的协程并不是线程安全的,对于一些场景,我们希望可以进行资源的控制,否则很容易造成数据的错误。
比如说 加N操作, A 和 B 都看到当前的数字为 10, A想加100,B想加 200 ,于是他们同时发出了修改请求,对于A 、B来说,正确的结果应该分别为 110和210, 但是最终的结果却是 310 。 为了保证正确的结果,在对其进行操作时,应该限制同时只能由单个人去处理。
资源的共享,必然会引起竞争,为了避免竞争所带来的的数据错误影响,我们应该保证共享的资源同一时刻只有一个人可以操作,这就是并发编程的弊端,通过锁的方式保证的数据一致性,却牺牲了部分性能。
在 go 语言中,锁主要分为两种:互斥锁和读写锁
3.1 互斥锁 (mutex)
我们先举一个没有锁机制的并发代码
package main import ( "fmt" "time" ) func main() { count := 0 for r := 0; r < 1000; r++ { go func() { count += 1 }() } time.Sleep(time.Second * 3) fmt.Println("the count is : ", count) }
此时count 的值一般是低于 1000 的,因为部分协程拿到的值是同一个,并且 count+=1 操作并不是原子操作。
//互斥锁是一个互斥锁。 //互斥锁的零值是未锁定的互斥锁。 //互斥锁不能在第一次使用后被复制。 type Mutex struct { state int32 sema uint32 } //Locker接口只有两个方法,一个加锁,一个解锁。 type Locker interface { Lock() Unlock() } //锁定当前的互斥量 //如果锁已被使用,则调用的 goroutine 将阻塞直到互斥锁可用。 func (m *Mutex) Lock() //对当前互斥量进行解锁 //如果在进入解锁时未锁定m,则为运行时错误。 //锁定的互斥锁与特定的 goroutine 无关。 //允许一个goroutine锁定Mutex然后安排另一个goroutine来解锁它。 func (m *Mutex) Unlock()
就可以解决并发导致的问题。
我们可以发现这种方式,是对某个资源的上锁,一旦这个资源被上锁,该资源则无法被抢占。
但是大多数情况,我们是希望可并发读不可并发写的。
3.2 读写锁 (RWMutex)
读写锁是针对读写操作的互斥锁,可以分别针对读操作与写操作进行锁定和解锁操作 。
其访问控制规则如下:
多个写操作之间是互斥的
写操作与读操作之间也是互斥的
多个读操作之间不是互斥的
在这样的控制规则下,读写锁可以大大降低性能损耗。
// RWMutex是一个读/写互斥锁,可以由任意数量的读操作或单个写操作持有。 // RWMutex的零值是未锁定的互斥锁。 //首次使用后,不得复制RWMutex。 //如果goroutine持有RWMutex进行读取而另一个goroutine可能会调用Lock,那么在释放初始读锁之前,goroutine不应该期望能够获取读锁定。 //特别是,这种禁止递归读锁定。 这是为了确保锁最终变得可用; 阻止的锁定会阻止新读操作获取锁定。 type RWMutex struct { w Mutex //如果有待处理的写操作就持有 writerSem uint32 // 写操作等待读操作完成的信号量 readerSem uint32 //读操作等待写操作完成的信号量 readerCount int32 // 待处理的读操作数量 readerWait int32 // number of departing readers } //读操作锁定 func (rw *RWMutex) RLock() //读操作解锁 func (rw *RWMutex) RUnlock() //写操作锁定 func (rw *RWMutex) Lock() //写操作解锁 func (rw *RWMutex) Unlock() //返回一个实现了sync.Locker接口类型的值 func (rw *RWMutex) RLocker() Locker
因为读写锁控制的多个读操作并不是互斥的,所以对于同一个读写锁,添加了多少个读锁定,必须要有等量的读解锁,否则其他协程将没有机会进行资源的操作。
Unlock方法会试图唤醒所有因为进行读锁定而被阻塞的协程
RUnlock 只会在已无任何读锁定的情况下,试图唤醒一个因欲进行写锁定而被阻塞的协程。
若对一个未被写锁定的读写锁进行写解锁,就会引发一个不可恢复的panic,同理对一个未被读锁定的读写锁进行读解锁也会如此。