Go语言进阶之并发编程 | 青训营笔记

Go语言进阶之并发编程 | 青训营笔记

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

前语

记录参加青训营的每一天的日笔记

并发编程

并发与并行的差异

并发:多线程程序在一个核的CPU上运转

并行:多线程程序在多个核的CPU上运转

Go能够充分发挥多核优势 高效运转

Go语言进阶之并发编程 | 青训营笔记

协程Goroutine

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

线程:内核态,线程跑多个协程,栈KB级别

Go语言进阶之并发编程 | 青训营笔记

线程的创建、切换、停止较大地占用系统资源

协程的创建和调度由Go言语进行完结

经过开启协程快速打印hello goroutine事例:

package concurrence
​
import (
    "fmt"
    "time"
)
​
func hello(i int) {
    println("hello goroutine : " + fmt.Sprint(i))
}
​
func HelloGoRoutine() {
    for i := 0; i < 5; i++ {
    // go关键字作为创建协程的关键字
        go func(j int) {
            hello(j)
        }(i)
    }
  // 确保子协程运转完前主线程不退出
    time.Sleep(time.Second)
}

CSP(communicating sequential processes)并发模型

不同于传统的多线程经过同享内存来通讯,CSP讲究的是“以通讯的办法来同享内存”。

Do not communicate by sharing memory; instead, share memory by communicating. “不要以同享内存的办法来通讯,相反,要经过通讯来同享内存。”

Channel 缓冲通道

创建办法:

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

通道是用来传递数据的一个数据结构,能够用于两个goroutine之间,经过传递一个指定类型的值来同步运转和通讯。

操作符<-用于指定通道的方向,完结发送or接纳

若未指定方向,则为双向通道

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

Go语言进阶之并发编程 | 青训营笔记

经过两个Channel通道完结数字平方使命事例:

package concurrence
​
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

若采用同享内存完结通讯,则会出现多个Goroutine一起操作一块内存资源的状况,这种状况会产生竞态问题(数据竞态)

Mutex互斥锁解决数据竞赛

互斥锁是一种常用的控制同享资源拜访的办法,它能够确保一起只要一个goroutine能够拜访同享资源。Go言语中运用sync包的Mutex类型来完结互斥锁。

package concurrence
​
import (
    "sync"
    "time"
)
​
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 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)
}
​
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()
}

运用互斥锁能够确保同一时间有且只要一个goroutine进入临界区,其他的goroutine则在等候锁;

当互斥锁开释后,等候的goroutine才能够获取锁进入临界区,多个goroutine一起等候一个锁时,唤醒的策略是随机的。

WaitGroup解决数据竞赛

Go言语中除了能够运用通道(channel)和互斥锁进行两个并发程序间的同步外,还能够运用等候组进行多个使命的同步,等候组能够确保在并发环境中完结指定数量的使命 WaitGroup 值在内部维护着一个计数,此计数的初始默认值为零。

package concurrence
​
import (
    "fmt"
    "sync"
)
​
func HelloPrint(i int) {
    fmt.Println("Hello WaitGroup :", i)
}
​
func ManyGoWait() {
    var wg sync.WaitGroup
    wg.Add(5)
    for i := 0; i < 5; i++ {
        go func(j int) {
            defer wg.Done()
            HelloPrint(j)
        }(i)
    }
    wg.Wait()
}
​
func main() {
    ManyGoWait()
}

小结

今天学习到的内容还需要进一步的消化,我也是打算将并发编程这一块的内容了解透彻了再进行下一部分的课程学习。假如笔记中有过错的当地也期望掘友们能够及时的提出纠正。