Go语言进阶与依赖管理(一) | 青训营笔记

68 阅读2分钟

这是我参与第五届青训营笔记创作活动的第二天

一,本堂课重点内容

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()//
}