这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
1.并发和并行的区别
并发是多线程程序在一个核的CPU上运行
并行是多线程程序在多个核的CPU上运行
由此可见,GO语言可以充分发挥多核优势,高效运行。
2.Goroutine
线程是系统里比较昂贵的资源,创建,切换,停止都消耗很多资源栈内存在KB级别,相比较而言携程更加轻量级栈内存在MB级别。 例:
func hello(i int) {
fmt.Println("hello goroutine" + fmt.Sprintln(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
结果为
3.CSP(Communicating Sequential Processes)
提倡通过通信共享内存,而不是通过共享内存实现通信。
左图的通道就是线程之间传递信息的通道,遵循先入先出,是有序的。
右图是共享内存实现通信,通过互斥量进行加锁,容易发生数据竟态的问题。
4.channel
channel是线程之间通信的通道,声明语法为make(chan 元素类型,【缓冲大小】)如:
make(chan int) //int类型无缓冲通道
make(chan int, 3) //int类型有缓冲通道
无缓冲通道的时候,发送和接收是同步的,也被称为同步通道。有缓冲通道可以实现队列结构,阻塞一定的数据。例:
func test() {
c1 := make(chan int)
c2 := make(chan int, 3)
go func() {
defer close(c1)
for i := 0; i < 10; i++ {
c1 <- i
}
}()
go func() {
defer close(c2)
for i := range c1 {
c2 <- i * i
}
}()
for i := range c2 {
//复杂操作
fmt.Println(i)
}
}
执行结果:
c2使用了有缓冲的通道,是因为考虑到消费者的消费速度比较慢,为防止其影响生产者的生产速度,所以有缓存区。
5.并发安全Lock
GO语言中也有锁,可以保证并发安全。
var (
x int64
lock sync.Mutex
)
func AddWithLock() {
for i := 0; i < 10; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func AddWithoutLock() {
for i := 0; i < 10; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 1000; i++ {
go AddWithLock()
}
time.Sleep(time.Second)
fmt.Println(x)
x = 0
for i := 0; i < 1000; i++ {
go AddWithoutLock()
}
time.Sleep(time.Second)
fmt.Println(x)
}
运行结果:
6.WaitGroup
此类有三个函数,计数器为0时 Wait停止阻塞,Add(n)计数器+n,Done完成后计数器-1。
代码示例:
func HelloGoRoutine() {
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()
}
运行结果同上 注意,此处运行Wg.Done之前要有defer关键字,意思时,执行完以下函数后才让计数器减一。
不同的模式
1.GOPATH模式 GOPATH模式中,把所有的源码放在src下,项目代码直接依赖src的代码。 弊端:如果项目AB同时依赖某一个包的不同版本,不可实现版本控制。 2.Go Vendor模式 每个项目引入一份依赖的副本,储存依赖的副本,解决了多个项目需要同一个包的依赖冲突的问题。 弊端:包也可以相互依赖,如果A项目依赖包BC,包BC同时依赖包D的不同版本,即导致冲突,编译出错。 3.Go Module模式 通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包。