Golang 的 并发编程

Golang 的 并发编程

并发编程

0 Go 中的并发

Goroutine 是由Go运行时管理的轻量级线程

go function(x, y)
package main

import (
	"fmt"
	"time"
)

func say(s string) {
	for range [10]int{} {
		time.Sleep(10 * time.Millisecond)
		fmt.Println(s)
	}
}

func main() {
	go say("world")
	say("hello")
}

Channels 管道 知识回顾

package main

import "fmt"

func main() {
	c := make(chan int, 10) // 缓冲管道 缓冲10个元素
	c <- 1   // 将值存入管道
	v := <-c // 取出值 先进先出
	fmt.Println(v)
}
c := make(chan int)
go func() {
	v := <-c // 当 channel 无缓存时 发送和接收 都会被阻塞 直到另一个goroutine在该channel上接收、发送一个值
	fmt.Println(v)
}()
c <- 1
time.Sleep(10 * time.Millisecond)

1 并发时同时修改全局变量,容易引起资源竞争

1.1 如何检测:

使用 -race 编译 资源竞争时报错:

go build -race main.go
package main

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

var (
	num  = 0
)

func main() {
	for i := 0; i < 1000; i++ {
		go runtimes(10)
	}

	time.Sleep(time.Second)
}

func runtimes(times int) int {
	num = num + 1
	for i := 1; i <= times; i++ {
		fmt.Printf("i = %d\n", times-i)
		fmt.Println(num)
		time.Sleep(time.Second)
	}
	return times
}
==================
WARNING: DATA RACE
Write at 0x0000005e1380 by goroutine 7:
i = 9
  main.runtimes()
      /root/project/golang/learn/main.go:19 +0x4a
  main.main.func1()
      /root/project/golang/learn/main.go:12 +0x30

Previous read at 0x0000005e1380 by goroutine 13:
3
  main.runtimes()
      /root/project/golang/learn/main.go:22 +0xd6
  main.main.func1()
      /root/project/golang/learn/main.go:12 +0x30

Goroutine 7 (running) created at:
  main.main()
      /root/project/golang/learn/main.go:12 +0x32

Goroutine 13 (running) created at:
  main.main()
      /root/project/golang/learn/main.go:12 +0x32
==================

1.2 如何避免

1.2.1 互斥锁

互斥锁只能同时有一个线程运行

var (
	num = 0
	lock sync.Mutex // 单词: Mutex 互斥
)

lock.Lock() 加锁

lock.Unlock() 解锁

func runtimes(times int) int {

	for i := 1; i <= times; i++ {
		lock.Lock()
		num = num + 1
		lock.Unlock()
		fmt.Printf("i = %d\n", times-i)
		fmt.Println(num)
	}
	return times
}

1.2.2 读写锁

当读操作大于写操作时 使用读写锁

读写锁(sync.RWMutex​)允许多读单写,适合读多写少的场景,增加并发读的效率。互斥锁(sync.Mutex​)一次只允许一个goroutine访问资源,不分读写,适用于读写操作频繁交替的情况。

package main

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

var RWLock sync.RWMutex
var list = make([]int, 0)

func W() {
	RWLock.Lock() // 获取写锁
	list = append(list, 1)
	RWLock.Unlock() // 释放写锁
}

func R() {
	RWLock.RLock() // 获取读锁
	fmt.Println(list)
	RWLock.RUnlock() // 释放读锁
}

func main() {
	go W()
	time.Sleep(10 * time.Millisecond) // 给写操作一点时间
	go R()
	go W()
	go W()
	go R()
	time.Sleep(100 * time.Millisecond) // 等待goroutines完成
	R()
}

1.2.3 管道

var intChan chan int

func main() {
	intChan = make(chan int, 10)
	sleep(1)

	fmt.Println(<-intChan)
}

func sleep(i int) {
	time.Sleep(time.Second)
	intChan <- i
}

LICENSED UNDER CC BY-NC-SA 4.0
Comment