这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
Goroutine
go中除了线程还有协程这一概念
- 协程:用户级,轻量级线程,栈可承载mb级别协程
- 线程:内核级别,一个线程可以跑多个协程,栈可承载KB级别线程
创建goroutine
func main() {
for i := 0; i < 5; i++ {
go func(j int) {
fmt.Println(j)
}(i)
}
time.Sleep(time.Second)//保证主线程在子协程执行完后退出
}
输出为:
0 3 4 1 2
go提倡通过通信来共享内存
当通过共享内存来实现通信时需要通过互斥变量对内存进行加锁以获得临界区的权限,这种情况下可能会影响系统的性能
Channel
channel是引用类型,所以通过make(chan 元素类型,缓冲区大小)进行创建
当省略缓冲区大小时为无缓冲通道
无缓冲通道可以使发送和接收的goroutine同步化
把数据往通道中发送时,如果接收方一直都没有接收,那么发送操作将持续阻塞。Go 程序运行时能智能地发现一些永远无法发送成功的语句并做出提示
当使用缓冲通道时,通道会在缓冲区满时产生阻塞
func calSquare() {
src := make(chan int)
des := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i//将数据写入通道中
}
}()
go func() {
defer close(des)
for i := range src {
des <- i * i
}
}()
for i := range des {
fmt.Println(i)
}
}
输出结果如下:
0 1 4 9 16 25 36 49 64 81
并发安全
并发程序对公共资源访问的限制最常见的就是使用互斥锁的方式。在Go中,sync.Mutex 提供了互斥锁的实现。
func test() {
var mutex sync.Mutex
count := 0
for r := 0; r < 50; r++ {
go func() {
mutex.Lock()
count += 1
mutex.Unlock()
}()
}
time.Sleep(time.Second)
fmt.Println("the count is : ", count)
}
当执行了 mutex.Lock() 操作后,如果有另外一个 goroutine 又执行了上锁操作,那么该操作被被阻塞,直到该互斥锁恢复到解锁状态。
go中除了互斥锁外还存在读写锁,读写锁是对读写操作进行加锁。需要注意的是多个读操作之间不存在互斥关系,这样提高了对共享资源的访问效率。
Go中读写锁由 sync.RWMutex 提供
var rwLock sync.RWMutex
rwLock.RLock()//获取读锁
rwLock.RUnlock()//释放读锁
rwLock.Lock()//获取写锁
rwLock.Unlock()//释放写锁
其中 Lock() 即“写锁定”,调用了“写锁定”后,不能有其他goroutine进行读或者写操作。 Unlock() 即“写解锁”,调用了“写解锁”后会唤醒所有因为要进行“读锁定(即:RLock())” 而被阻塞的 goroutine。
RLock()为“读锁定”,调用“读锁定”后,不能有其他goroutine进行写操作,但是可以进行读操作。RUnlock() 为“读解锁”,调用“读解锁”后,会唤醒一个因为要进行“写锁定”而被阻塞的goroutine。
WaitGroup
WaitGroup能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。
它本质上是维护了一个计数器,对外暴露了三个方法
- Add 用来添加 goroutine 的个数
- Done 执行一次数量减 1
- Wait 用来等待结束
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
fmt.Println(j)
wg.Done()
}(i)
}
wg.Wait()
}