GO语言进阶与工程管理(part 1) | 青训营笔记

85 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

一、主要内容

  • 并发编程
    • 协程Goroutine
    • 通道Channel
    • 锁和并发安全
    • 并发安全Lock

二、内容详解

并发编程

  • 并发和并行的直观理解

任务一和任务二并发执行

image.png

任务一和任务二并行执行

image.png

  • 协程与线程的比较

协程 : 用户态,轻量级,栈KB级别

线程 : 内核态,一个线程可以跑多个协程,栈MB级别

package main

import (
   "fmt"
   "time"
)

func main() {
   for i := 0; i < 5; i++ {
      go func(j int) {
         hello(i)
      }(i)
   }
   time.Sleep(time.Second)
}

func hello(i int) {
   fmt.Printf("Hello go routine %v", i)
}

输出结果乱序

  • Channel

Channel是Goroutine之间的通信机制:

image.png

创建通道实例:

通道实例 := make(chan 数据类型)

通过通道发送和接收数据:

通道变量 <- 值

使用通道做并发同步的示例程序:

package main
import (
"fmt"
)
func main() {
   // 构建一个通道
   ch := make(chan int)
   // 开启一个并发匿名函数
   go func() {
      fmt.Println("start goroutine")
      // 通过通道通知main的goroutine
      ch <- 0
      fmt.Println("exit goroutine")
   }()
   fmt.Println("wait goroutine")
   // 等待匿名goroutine
   <-ch
   fmt.Println("all done")
}
  • 并发安全Lock

当多个协程对同一个数据项进行操作,应当对该数据项进行加锁,否则会因为冲突产生错误。

package main

import (
   "fmt"
   "sync"
   "time"
)

func main() {
   add()
}

var (
   x    int64
   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 add() {
   x = 0
   for i := 0; i < 5; i++ {
      go addWithLock()
   }
   time.Sleep(time.Second)
   fmt.Println("addWithLock result", x)

   x = 0
   for i := 0; i < 5; i++ {
      go addWithoutLock()
   }
   time.Sleep(time.Second)
   fmt.Println("addWithoutLock result", x)
}

上述程序分别采用加锁和不加锁的方式,创建5个协程对变量x递增2000,我们期望运行结束后x的值为10000。

运行程序十次,加锁的结果每次都是10000,而不加锁的结果是未知的,只有很小的概率成功。

image.png