这是我参与第五届青训营笔记创作活动的第二天
一,本堂课重点内容
Goroutine
Channel
Sync
二,详细知识点介绍
并发 VS 并行
并发是多线程程序在一个核的cpu上运行(主要通过时间片的切换)
并行是多线程程序在多个核的cpu上运行(直接同时运行,因为有多个核)
协程(Goroutine) VS 线程
线程我们都很熟悉,在学习Java的时候就有学习多线程,比较消耗资源,很重
协程可以说是一种很轻的线程,用户级的线程创建和调度由Go语言本身来实现(线程跑多个协程)
协程的一个小例子
打印出来的顺序是随机的,这里用sleep做了一个阻塞,保证子协程结束之前,主协程不退出
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
CSP
go提倡通过通信共享内存,而不是通过共享内存而实现通信
Channel
一种引用类型,创建需要make关键字,包含元素类型和缓冲大小,根据缓冲大小可分为无缓冲通道和有缓冲通道,就想老师在课中举例一样,缓冲相当于“快递柜”
当使用无缓冲通道时,会使发送的Goroutine和接收的Goroutine同步化
func CalSquare() {
src := make(chan int)//无缓存队列
dest := make(chan int, 3)//有缓存通道,可以解决生产和消费之间不同步的问题
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)//做了一个延迟的资源关闭
for i := range src {
dest <- i * i
}
}()
for i := range dest {
//复杂操作
println(i)
}
}
并发安全 Lock
这里的例子是对变量执行2000次+1的操作,并且5个协程并发执行,对比了加锁和不加锁两种情况的运行
var (
x int64
lock sync.Mutex
)
func addWithLock() {//10000
for i := 0; i < 2000; i++ {
lock.Lock()//获取临界区的资源
x += 1
lock.Unlock()//再把权限释放掉
}
}
func addWithoutLock() {//<=10000,有并发安全问题
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:", x)
}
WaitGroup
有三个方法 Add(delta int) Done() Wait(),内部就是维护了一个计时器,当计时器为零时表明所有并发任务完成
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)//
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()//
hello(j)
}(i)
}
wg.Wait()//
}