Go语言入门指南:并发编程学习|青训营

82 阅读2分钟

00416-2664387232-masterpiece,(best quality),(illustration),(colorful), __,1girl,green hair.png


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)

image.png Go语言提倡通过通信共享内存而不是通过共享内存实现通信

Channel

make (chan 元素类型,[缓冲大小])

  • 无缓冲通道 make(chan int)
  • 有缓冲通道 make(chan int,2)

有缓存的通道能够解决消费者消费过快而生产跟不上的情况

image (1).png


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

image (2).png

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