go的并发编程 | 青训营

69 阅读4分钟

概念

并发/并行

  • 并发指的是多线程序在一个CPU同时相应后,安排不同时间运行
  • 并行是多个CPU同时运行多个线程

协程/线程

  • 协程是用户态,轻量级的线程,栈KB级别
  • 线程是内核态,线程跑多个协程,栈MB级别

阻塞/非阻塞

  • 阻塞是数据未返回时会等待
  • 非阻塞是不需要等待数据返回

同步/异步

  • 同步是发起调用时,等待返回结果
  • 异步是发起调用后,调用结束,有结果之后才会通知 举个例子就是,去医院看抽血,在那里抽血完不做别的事情等待结果,就是同步,如果抽血完回去做别的事情,等待医院通知结果,再去拿结果,就是异步

通过通道共享内存

![[Pasted image 20230725154143.png|600]]

sync锁

概念

互斥锁

  • 多个任务需要处理同一个资源,但是同一时间内只有一个能使用该资源
  • 通过lock(上锁)unlock(解锁)方式控制,保证同一时间内只有一个能使用该资源

读写锁

  • 多个线程允许读,但是只有一个线程允许写入
  • 同一时间多个线程都可以申请读锁,但是只有一个线程能申请读锁(申请到写锁之后,读锁也需要等待写入完成)
  • 用于频繁读取但是不频繁写入的场景

包引入

import "sync"

sync.Mutex 互斥锁

mutex := &sync.Mutex{}

mutex.Lock()   // 上锁
// 操作
mutex.Unlock() // 解锁

sync.RWMutex 读写锁

mutex := &sync.RWMutex{}

mutex.Lock()    // 加写锁
// 操作
mutex.Unlock()  // 解写锁

mutex.RLock()   // 加读锁
// 操作
mutex.RUnlock() // 解读锁

sync.WaitGroup

  • WaitGroup 是一个计数器,n个并发任务时计数n,当n=0时结束
var wg sync.WaitGroup

func hello(){
	// 表示该协程以及结束,计数器 -1
	defer wg.Done()
	fmt.Println("hello world")
}

func main(){
	for i = 0; i < 2; i++ {
		// 计数器加1
		wg.Add(1)
		// 两个协程执行hello()
		go hello()
	}
	// 阻塞直到变0
	wg.Wait()
}

sync.Once

  • 应用于某些高并发场景但是某些只执行一次的操作
  • 比如只配置一次文件,只关闭一次通道
var loadonce sync.Once

func handle(){
	code
}

// 函数只执行一次
loadonce.Do(handle())

sync.Map

  • go内置的map类型在并发条件下并不是安全的
var m sync.Map

func main(){
	// 写入
	m.Store("name", "aaa")
	// 读取
	name,data := m.Load("name")
	// 删除
	m.Delete("name")
	// 读取或写入(如果存在则读出原值,没有则写入)
	m.LoadOrStore("name": "bbb")
	// 遍历
	m.Range(func(key, value interface{}) bool {
		fmt.Println(key, value)
		return true
	})
}

atomic原子操作

使用sync应该保护一段逻辑,而不是一段变量,如果只是涉及到某个int或string类型上锁,应该使用原子操作

import "atomic"

var a int64

// 读取操作
atomic.LoadInt64(x)
// 写入操作
atomic.StoreInt64(&x)
// 

创建协程goroutines

  • 正常情况下,两个函数执行是有先后顺序的
  • 但是创建协程之后两个函数的调用可能发生在同一时间
// 在go关键字后面添加函数即可创建channel
go func(){
	code
}(data)

select选择器

多个chan等待时,如果直接使用多个 s := <-ch 性能会很差,所以使用select

ch1 := make(chan int)
ch2 := make(chan int)

select {
	case s1 := <- ch1:   // ch1获取到数据时执行
		code
	case s2 := <- ch2:   // ch2获取到数据时执行
		code
}

context上下文

channel信道

概念

  • channel相当于队列,遵循先进先出原则
  • 相当于一个线程(go的线程是并发的)
  • 并发:同时响应,交替执行
  • 并行:同时响应,同时执行
  • 阻塞:在结果返回前,该线程被挂起,直到得到结果
  • 非阻塞:如果不能立即得到结果,返回错误信息(需要定时轮询查看状态)

初始化

  • 不带缓冲的通道,进出会阻塞(信道无法储存数据,需要)
  • 带缓冲的通道,进一次长度+1,出一次长度-1
// 不带缓冲的channel 选择你需要进行 
ch := make(chan type)

// 带11个缓冲的(0-10)channel  表示队列的长度
// 当队列里面数据满了之后,再进入就会出现阻塞
ch := make(chan string,10)

// 只读通道                   只写通道
ch := make(<-chan string)    ch := make(chan<-string)

读写操作

ch := make(chan int)

// 写入ch
ch <- 2

// 读取ch 保存到x中 
x := <-ch

信道长度和容量

ch := make(chan int,10)

// 查看信道可容纳的数据
cap(ch)

// 查看信道当前的长度
len(ch)