12.并发

并发和并行

并发:同一时间内执行多个任务

并行:同一时刻执行多个任务

goroutine

goroutine的概念类似于线程,但不需要自己去写进程、线程、协程,只需要把任务包装成一个函数,开启一个goroutine去执行这个函数就可以了

使用goroutine

只需要在调用函数的时候,前边加上go关键字,就可以为一个函数创建一个goroutine,一个goroutine必定对应一个函数,可以创建多个goroutine去执行相同的函数

启动单个goroutine

func hello(){
  fmt.Println("Hello")
}

func main(){
  go hello()  // 开启一个独立的goroutine去执行hello这个函数
  fmt.Println("主线程done!")
}

上面这个例子可能会只打印主线程done!不打印Hello,原因是在程序启动的时候,Go程序会为主函数main()创建一个goroutine,主函数结束后,会把所有子goroutine关掉,而在上面的例子中,main()函数结束的太快了,在hello()执行前就结束了,所以没有打印Hello

可以使用WaitGroup来解决

package main

import (
	"fmt"
	"sync"
)

// 声明WaitGroup变量
var wg sync.WaitGroup 

func hello() {

	fmt.Println("Hello")
	wg.Done() // 标记任务结束
}

func main() {
	wg.Add(1) // 计数器+1
	go hello()
	fmt.Println("主线程")
	wg.Wait() // 等待所有任务结束
}

启动多个goroutine

package main

import (
	"fmt"
	"sync"
)

// 声明WaitGroup变量
var wg sync.WaitGroup 

func hello() {

	fmt.Println("Hello")
	wg.Done() // 标记任务结束
}

func main() {
  for i:=0; i<100;i++{
		wg.Add(1) // 计数器+1
		go hello()
  }
	fmt.Println("主线程")
	wg.Wait() // 等待所有任务结束
}

使用匿名函数启动goroutine

package main

import (
	"fmt"
	"sync"
)

// goroutine
var wg sync.WaitGroup

func main() {

	for i := 0; i < 10000; i++ {
		wg.Add(1)
		go func() {
			fmt.Println("hello", i)
			wg.Done()
		}()
	}
	fmt.Println("主程序done")
	wg.Wait()
}

GOMAXPROCS()

runtime.GOMAXPROCX(1)指定几个物理核心来执行

go 1.5以后得版本默认有多少核心就使用多少核心

channel

channel是一种引用类型,与队列性质相同,遵循先进先出原则。通道类型的空值是nil,通道声明以后需要使用make函数初始化后才可以使用,声明格式如下:

var 变量名称 chan 元素类型

var ch1 chan int  // 声明一个传递整形的通道
var ch2 chan bool // 声明一个传递bool类型的通道
var ch3 chan []int// 声明一个传递int切片的通道

make(chan 元素类型,[缓冲大小]) // 缓冲大小可以不写

ch4:= make(chan int)
ch5:= make(chan bool, 1)

channel 操作

package main

import "fmt"

// channel demo
func main() {
	// 声明一个int类型的channel
	var ch1 chan int
	// 初始化channel
	ch1 = make(chan int, 1)
	// 往通道中存放一个值
	ch1 <- 10
	fmt.Println(ch1)

	// 从通道中取出一个值
	x := <-ch1
	fmt.Println(x)
  
  // 取通道中元素的数量
  len(ch1)
  // 拿到通道的容量
  cap(ch1)
  // 这两个操作几乎不会用到
  
	// 关闭通道
	close(ch1) // 关闭操作不是必须的,无用通道会被垃圾回收掉
}

无缓冲区的通道

ch := make(chan int)
ch1 <- 10 // 报错,deadlock

无缓冲区的通道(同步通道)和带缓冲区通道的区别

无缓冲的通道只有在接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的状态。

有缓冲的通道可以在通道内存放指定数量的数据,等待接收方来取,通道内存放满时,便不能再存放了

goroutine和channel协同

// 生成两个goroutine,两个channel
// 1. 生成0-100的数字发送到ch1
// 2. 从ch1中取出数据计算它的平方,把结果放到ch2中

package main

import "fmt"

// 1.生成0-100的数字发送到ch1
func f1(ch chan int) {
	for i := 0; i < 100; i++ {
		ch <- i
	}
	close(ch) // 通道关闭后也可以继续取值
}

// 2. 从ch1中取出数据计算它的平方,把结果放到ch2中
func f2(ch1, ch2 chan int) {
	for {
		tem, ok := <-ch1
		if ok {
			ch2 <- tem * tem
		} else {
			break
		}

	}
	close(ch2)
}

func main() {
	ch1 := make(chan int, 100)
	ch2 := make(chan int, 200)
	go f1(ch1)
	go f2(ch1, ch2)
// 可以通过for range的方式从通道中取值
	for ret := range ch2 {
		fmt.Println(ret)
	}
}

单向通道

  1. 只接收的通道
func f1(ch1 chan<- int){ // 在形参的类型后增加标识,表示只存
  x := <-ch1 // 会报错
}
  1. 只取值的通道
func f2(ch2 <-chan int){ // 在形参的类型前增加标识,表示只取
  ch2 <- 1 // 报错
}

通道状态总结

img

对已经关闭的通道再次执行 close,也会引发 panic

select 多路复用

select 类似于switch 语句

select{
  case <-ch1:
  ...
case data := <-ch2:
  ...
  case ch3<-1:
  ...
}
  • 可处理一个或多个channel的发送/接收操作
  • 如果多个case同时满足,select会随机选择一个
  • 对于没有case的select会一直等待,可用于阻塞main函数

并发安全和锁

多个goroutine同时操作同一个资源的时候,会发生资源冲突

var x int
var wg sync.WaitGroup
func add(){
  for i:=0;i<5000;i++{
    x = x+1
  }
  wg.Done()
}

func main(){
  wg.Add(2)
  // 开启两个goroutine,同时操作同一个全局变量
  go add()
  go add()
  wg.Wait()
  fmt.Println(x) // 正常的运行结果应该是10000,但实际可能不到10000
}

互斥锁sync.Mutex

保证同一时间内只有一个goroutine能够获得锁

package main

import (
	"fmt"
	"sync"
)

var x int
var wg sync.WaitGroup
var lock sync.Mutex // 互斥锁

func add() {

	for i := 0; i < 5000; i++ {
		lock.Lock() // 加锁
		x = x + 1
		lock.Unlock() // 解锁
	}
	wg.Done()

}

func main() {
	wg.Add(2)
	// 开启两个goroutine,同时操作同一个全局变量
	go add()
	go add()
	wg.Wait()
	fmt.Println(x) // 正常的运行结果应该是10000,但实际可能不到10000
}

读写互斥锁sync.RWMutex

互斥锁是完全互斥的,但很多场景下是读多写少的

读写锁分为两种:读锁和写锁。当一个goroutine获取读锁以后,其他的goroutine如果获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获得写锁后,其他goroutine无论获取什么锁都会等待。

package main

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

var (
	x  int
	wg sync.WaitGroup
	rw sync.RWMutex
)

func read() {
	rw.RLock()  // 只有读取的时候加的锁不一样
	time.Sleep(time.Millisecond)
	rw.RUnlock() // 只有读取的时候加的锁不一样
	wg.Done()
}

func write() {
	rw.Lock()
	x = x + 1
	time.Sleep(time.Millisecond * 10)
	rw.Unlock()
	wg.Done()
}

func main() {
	start := time.Now()
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go read()
	}

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go write()
	}
	wg.Wait()
	fmt.Printf("%s\n", time.Since(start))
}

sync.Once

保证函数只执行一次,可以用来初始化或者关闭资源(比如多个goroutine关闭channel)

var icons map[string]image.Image
var loadIconsOnce sync.Once

func loadIcons(){
  icons = map[string]image.Image{
    "left": loadicon("left.png"),
		"right": loadicon("right.png"),
		"up": loadicon("up.png"),
		"down": loadicon("down.png"),
  }
}

func Icon(name string) image.Image{
  loadIconsOnce.Do(loadIcons)  // 保证初始化icons的操作只执行一次
  return icons[name]
}

sync.Map

go语言中内置的map不是并发安全的

package main

import (
	"fmt"
	"sync"
)

var m = make(map[int]int)
var wg sync.WaitGroup

func get(key int) int {
	return m[key]
}

func set(key int, value int) {
	m[key] = value
}

func main() {
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go func(i int) {
			set(i, i+100)
			fmt.Printf("key:%v, value:%v", i, get(i))
			wg.Done()
		}(i)
	}
	wg.Wait()
}

以上这段代码执行会报错,因为go语言中内置的map不是并发安全的,这种场景可以自己加锁来实现,也可以直接使用syncn.Map

package main

import (
	"fmt"
	"sync"
)

var m = sync.Map{}
var wg sync.WaitGroup

func main() {
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go func(i int) {
			m.Store(i, i+100)// sync.Map中写数据
			value, _ := m.Load(i)  // 读取sync.Map的值
			fmt.Printf("key:%v, value:%v\n", i, value)
			wg.Done()
		}(i)
	}
	wg.Wait()
}

sync.Map的打印(Range方法)

package main

import (
	"fmt"
	"sync"
)

func main() {
	var m sync.Map

	// 添加数据
	m.Store("name", "Alice")
	m.Store("age", 30)
	m.Store("job", "Engineer")

	// 打印 sync.Map
	fmt.Print("sync.Map contents: {")
	first := true
	m.Range(func(key, value interface{}) bool {
		if !first {
			fmt.Print(", ")
		}
		fmt.Printf("%v: %v", key, value)
		first = false
		return true
	})
	fmt.Println("}")
}
posted @ 2025-06-27 11:06  L大官  阅读(15)  评论(0)    收藏  举报