Goroutine
- 协程:用户态,轻量级线程,栈KB级别
- 线程:内核态,线程跑多个协程,栈Mb级别
Go如何开启一个线程呢?
只需要在调用函数的前面加上一个关键字Go
func hello(i int) {
fmt.Println("hello goroutine : " + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
go func(j int) {
hello(j)
}(i)
}
}
CSP(Communicating Sequential processes)
Go语言提倡通过通信共享内存而不是通过共享内存实现通信
Channel
make (chan 元素类型,[缓冲大小])
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int,2)
有缓存的通道能够解决消费者消费过快而生产跟不上的情况
func main() {
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 := 0; i < 10; i++ {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
defer一般用于资源的释放和异常的捕捉, 作为Go语言的特性之一. defer 语句会将其后面跟随的语句进行延迟处理. 意思就是说 跟在defer后面的语言 将会在程序进行最后的return之后再执行. 在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
并发安全Lock
在多线程环境下,经常会设有临界区, 我们这个时候只希望同时只能有一个线程进入临界区执行,可以利用操作系统的原子操作来构建互斥锁 ,这种方式简单高效,但是却无法处理一些复杂的情况,例如:
- 锁被某一个线程长时间占用,其他协程将无意义的空转等待,浪费CPU资源
- 因为锁是大家一起在抢,所以某些线程可能一直都抢不到锁
为了解决上述问题,在操作系统的内部会为锁构建一个等待队列 , 用于之后的唤醒,防止其其一直空转。操作系统级别的锁会锁住整个线程,并且锁的抢占也会发生上下文切换。
在Go语言中,拥有比线程更加轻量的协程,并且也在协程的基础之上实现了更加轻量级的互斥锁。
var x int64
var lock sync.Mutex
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func main() {
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)
}
结果:
WithoutLock : 9745
WithLock 10000
addWithLock()函数每次对x进行加1之前,都会对互斥锁进行访问,来获取资源,一旦抢占到资源后,则资源就会被锁住,其他的线程就不能够进行访问从而进入等待队列,当前线程操作完之后则会释放线程,等待其他线程进行抢占,通过这种方式来保证一段时间内只有一个线程对资源进行操作,对锁想要更深一步了解可以看下这篇文章blog.csdn.net/weixin_4482…
WaitGroup
计数器:
开启协程+1;执行结束-1;主协程阻塞直到计数器为0
func main() {
var wq sync.WaitGroup
wq.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wq.Done()
hello(j)
}(i)
}
wq.Wait()
}
func hello(j int) {
println("hello goroutine :", j)
}