golang 并发

1、并发和并行

go语言协程依赖于线程,即使处理器运行的是统一线程,在线程内部go语言调度器也会切换多个协程执行,这个时候协程是并发的。如果多个协程被分配给了不同的线程,这些线程被不同的CPU核心处理,协程就是并行处理的。
故多核处理场景下,go语言的协程是并发

并发
在这里插入图片描述
并行
在这里插入图片描述

2、线程和协程的区别

协程是轻量级的线程

1、调度方式
协程是用户态的,协程和线程的对应关系是M:N。Go语言调度器可以将多个协程调度到同一个线程中,一个协程也可能切换到多个协程中执行
2、上线文切换速度
协程的速度要快于线程,是因为协程切换不用经过操作系统用户态和内核太的切换,并且协程切换的时候保留的寄存器要少于线程,线程切换大约需要1~2微秒,协程约为0.2微秒
3、调度策略
线程的调度大多数时间是抢占式的,操作系统调度器为了均衡每个线程的执行周期,会定时发出中断信号轻质执行线程上下文切换。而Go语言中的协程一般情况下是协作式调度,当一个协程处理完自己的任务后,可以主动将执行权限让给其他协程。这意味着协程可以更好的在规定时间内完成自己的工作,不会被轻易抢占。当一个协程运行了过长的时间,Go语言调度器才会抢占其执行
4、栈大小
线程的栈大小为2MB(避免栈一处),协程的栈默认为2KB。同时协程的栈在运行的时候是不能更改的,运行时会动态检测栈大小,动态扩容

3、golang 并发实现

goroutine 不是os线程、不是绿色线程(由语言运行时管理的线程),是协程。协程是一种非抢占式的简单并发子goroutine(函数、闭包或方法),也就是说,它们不能被中断。取而代之的是,协程有多个点,允许暂停或重新进入 —Go语言并发之道

  • goroutine是go的并发体
  • channelsgoroutine的通信机制(实际就是发送数据)

基础知识

goroutine

goroutine是一种轻量级的实现,可以在单个进程中执行成千上万的并发任务,是go语言并发设计的核心.
将关键字go放在一个函数的前面,这个函数执行时就会成为一个独立的并发线程,这线程就被称为goroutine

channel 通道

通道(chan)是一种特殊的类型,是一种引用类型。
主要分类有单向通道无缓冲的通道带缓冲的通道
主要作用是同步信号(shutdown/close/finish) 消息传递(queue/stream) 互斥(mutex)

//go中对chan的定义   /runtime/chan.go
type hchan struct {
	qcount   uint           // total data in the queue    数据总量
	dataqsiz uint           // size of the circular queue
	buf      unsafe.Pointer // points to an array of dataqsiz elements
	elemsize uint16
	closed   uint32      //标记channel已经关闭
	elemtype *_type // element type
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters    和ring buffer相关
	sendq    waitq  // list of send waiters    和ring buffer相关

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex    //保护所有数据结构,但在某些quick path场景下不必加锁
}

type waitq struct {
	first *sudog
	last  *sudog     //sudog dequeue中等待的goroutine以及他的数据存储
}

底层原理参考

  • 任何时间只能有一个goroutine访问chan
  • chan 里面的数据满足FIFO规则
//声明通道
var 通道变量  chan  通道类型
//创建通道
通道实例  :=  make(chan 通道类型)
//给通道发送数据
通道变量  <-//读取通道数据
data, ok := <-通道变量
defer关键字

注意defer会对表达式进行提前求值

go调度模型

go调度模型

在这里插入图片描述
G:goroutine
M:系统线程 -> 执行代码的实体
P:Processor调度实体

实现方式

syn包

进行多个任务的同步,主要操作有Add()、Done()、Wait()等操作

  • 互斥锁和读写锁

互斥锁 sync.Mutex:[互斥锁原理] (https://www.jb51.net/article/258684.htm)一个goroutine独占资源 互斥性、公平调度,饥饿处理
读写锁 sync.RWMutex:不可递归调用,读写互斥,多读之间并发

使用注意事项:

1、配套使用Lock、UnLock
2、运行时离开当前逻辑就释放锁
3、锁的粒度越小越好,加锁后尽快释放锁
4、没有特殊原因,尽量不要defer释放锁
5、RWMutex的读锁不要嵌套使用

  • List item
  • cond

sync.Cond 主要用于goroutine之间的协作,主要有三个函数Broadcast() , Signal(), Wait(), 一个成员变量,L Lock
Broadcast() :唤醒在本cond上等待的所有的goroutine
Signal():选择一个goroutine进行唤醒
Wait():让goroutine进行等待

  • once

sync.Once 中有一个Do()方法,无论是否更换Do()里面的东西,这个方法只会执行一次

func main() {
	var count int
	increment := func() {
		count++
	}

	var once sync.Once
	var increments sync.WaitGroup
	increments.Add(100)
	for i:=0;i<100;i++{
		go func() {
			defer increments.Done()
			once.Do(increment)
		}()
	}

	increments.Wait()
	fmt.Printf("count is %d\n", count)
}
//最终输出为 1

池(pool)是Pool模式的并发安全实现,需要参照pool设计模式

channel
select 语句
GOMAXPROCS控制
runtime.GOMAXPROCS(runtime.NumCPU())   //充分利用cpu资源

Go语言的并发通过goroutine`实现。goroutine类似于线程,属于用户态的线程,我们可以根据需要创建成千上万个goroutine并发工作。goroutine是由Go语言的运行时(runtime)调度完成,而线程是由操作系统调度完成。

Go语言还提供channel在多个goroutine间进行通信。goroutine和channel是 Go 语言秉承的 CSP(Communicating Sequential Process)并发模式的重要实现基础。

经典例子分析

case1
func handler(){
	ch := make(chan string)
	go func() {
		time.Sleep(3*time.Second)
		ch <- "job result"
	}()

	select {
	case result := <- ch:
		fmt.Println(result)
	case <-time.After(time.Second):
		return
	}
}

上述代码会造成goroutine泄漏,原因在于channel没有缓存
可能造成goroutine泄漏的原因有:
1、channel没有缓存
2、select命中timeout逻辑
3、导致channel没有消费者
4、最终导致anonymous goroutine泄漏

case2

var mu sync.RWMutex
func main() {
	go A()
	time.Sleep(2*time.Second)

	fmt.Println("main call lock")
	mu.Lock()
	defer mu.Unlock()
}

func A()  {
	fmt.Println("A call rlock")
	mu.RLock()
	fmt.Println("A rlocked")
	defer mu.RUnlock()
	B()
}

func B(){
	time.Sleep(5*time.Second)
	C()
}

func C()  {
	fmt.Println("C call rlock")
	mu.RLock()
	fmt.Println("C rlocked")
	mu.RUnlock()
}

运行结果如下:
在这里插入图片描述

谨慎使用锁的递归调用,上面的还需再看一看

case3

go的map不可以并发

go内置map的几个要点:参考

  • hash算法:AES
  • 冲突解决:链地址法(和java类似) Python采用开放定址法
  • range go每次rangemap的时候顺序都会不同,因为go故意实现了random 【需要代码论据】
  • 装填因子 6.5
  • rehash 渐进式rehash (和redis类似)

Semaphore信号量

自旋锁

### Golang 并发模型详解 #### 轻量级协程(Goroutine) Golang并发模型核心在于轻量级的协程——goroutine。这些 goroutine 是由 Go 运行时自动管理的小型函数执行单元,具有非常低的资源开销[^1]。 ```go func main() { go func() { fmt.Println("This runs concurrently.") }() } ``` 当启动一个 goroutine 时,只需在函数前加上 `go` 关键字即可让该函数异步运行。这种机制使得开发者能够轻松地并行处理多个任务而不必担心底层线程管理和调度复杂度。 #### CSP 风格的通道(Channel) 为了使不同 goroutine 之间能安全有效地交换信息,Golang 提供了基于 CSP(Communicating Sequential Processes)理论设计出来的 channel 结构体作为通信桥梁[^3]。 - **单向通道**:只允许发送(`chan<-`) 或接收(`<-- chan`) - **双向通道**:既可读也可写 (`chan int`, etc.) 下面是一个简单的生产者-消费者模式例子: ```go package main import ( "fmt" ) // Producer function that sends values into a channel. func producer(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i * 2 // Send value on the channel fmt.Printf("Produced %d\n", i*2) } close(ch) // Close after all items are produced } // Consumer function that reads from a channel until it's closed. func consumer(ch <-chan int) { for num := range ch { // Range over channel till close() fmt.Printf("Consumed %d\n", num) } } func main() { channel := make(chan int) go producer(channel) consumer(channel) } ``` 此代码展示了如何利用 channels 实现两个独立工作的 goroutines 之间的同步与通讯过程. #### 安全的数据共享方式 由于 Goroutines 和 Channels 的存在,在大多数情况下不需要显式的锁定原语来保护临界区或协调访问共享变量。相反的是,应该尽可能采用消息传递的方式来进行跨 task 数据交互. 例如: ```go var sharedValue string ch := make(chan struct{}) go func() { defer close(ch) sharedValue = "Updated Value" }() <-ch // Wait for update completion before continuing... fmt.Println(sharedValue) ``` 这里通过关闭 channel 来通知另一个 goroutine 更新已完成,从而避免了直接操作全局状态带来的竞态条件风险。 #### 系统调用阻塞处理 对于可能引起长时间等待的操作如 I/O 请求等场景下,Go runtime 设计了一套巧妙的方法确保不会因某个特定 goroutine 的挂起影响到整个应用程序性能[^4]. 当 M (OS Thread) 上正在运行的一个 G(goroutine) 发生系统调用或其他形式的阻塞行为时,M 将被暂时解除绑定关系以便其他可用的工作窃取工作者(P) 可继续分配新的工作给闲置中的 OS Threads 或创建额外的新实例满足需求.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值