
正文
go语言共享内存通信 go 内存共享
提示:扫一扫查出行【扫一扫了解最新限行尾号】
复制提示
goroutine是不是内存共享
goroutine和channel是Go语言非常棒的特色,它们提供了一种非常轻便易用的并发能力。但是当您的应用进程中有很多goroutine的时候,如何在主流程中等待所有的goroutine 退出呢?
1 通过Channel传递退出信号
Go的一大设计哲学就是:通过Channel共享数据,而不是通过共享内存共享数据。主流程可以通过channel向任何goroutine发送停止信号,就像下面这样:
func run(done chan int) {
for {
select {
case -done:
fmt.Println("exiting...")
done - 1
break
default:
}
time.Sleep(time.Second * 1)
fmt.Println("do something")
}
}
相关问答
Q1: Golang 语言深入理解:channel
本文是对 Gopher 2017 中一个非常好的 Talk�: [Understanding Channel](GopherCon 2017: Kavya Joshi - Understanding Channels) 的学习笔记,希望能够通过对 channel 的关键特性的理解,进一步掌握其用法细节以及 Golang 语言设计哲学的管窥蠡测。
channel 是可以让一个 goroutine 发送特定值到另一个 gouroutine 的通信机制。
原生的 channel 是没有缓存的(unbuffered channel),可以用于 goroutine 之间实现同步。
关闭后不能再写入,可以读取直到 channel 中再没有数据,并返回元素类型的零值。
gopl/ch3/netcat3
首先从 channel 是怎么被创建的开始:
在 heap 上分配一个 hchan 类型的对象,并将其初始化,然后返回一个指向这个 hchan 对象的指针。
理解了 channel 的数据结构实现,现在转到 channel 的两个最基本方法: sends 和 receivces ,看一下以上的特性是如何体现在 sends 和 receives 中的:
假设发送方先启动,执行 ch - task0 :
如此为 channel 带来了 goroutine-safe 的特性。
在这样的模型里, sender goroutine - channel - receiver goroutine 之间, hchan 是唯一的共享内存,而这个唯一的共享内存又通过 mutex 来确保 goroutine-safe ,所有在队列中的内容都只是副本。
这便是著名的 golang 并发原则的体现:
发送方 goroutine 会阻塞,暂停,并在收到 receive 后才恢复。
goroutine 是一种 用户态线程 , 由 Go runtime 创建并管理,而不是操作系统,比起操作系统线程来说,goroutine更加轻量。
Go runtime scheduler 负责将 goroutine 调度到操作系统线程上。
runtime scheduler 怎么将 goroutine 调度到操作系统线程上?
当阻塞发生时,一次 goroutine 上下文切换的全过程:
然而,被阻塞的 goroutine 怎么恢复过来?
阻塞发生时,调用 runtime sheduler 执行 gopark 之前,G1 会创建一个 sudog ,并将它存放在 hchan 的 sendq 中。 sudog 中便记录了即将被阻塞的 goroutine G1 ,以及它要发送的数据元素 task4 等等。
接收方 将通过这个 sudog 来恢复 G1
接收方 G2 接收数据, 并发出一个 receivce ,将 G1 置为 runnable :
同样的, 接收方 G2 会被阻塞,G2 会创建 sudoq ,存放在 recvq ,基本过程和发送方阻塞一样。
不同的是,发送方 G1如何恢复接收方 G2,这是一个非常神奇的实现。
理论上可以将 task 入队,然后恢复 G2, 但恢复 G2后,G2会做什么呢?
G2会将队列中的 task 复制出来,放到自己的 memory 中,基于这个思路,G1在这个时候,直接将 task 写到 G2的 stack memory 中!
这是违反常规的操作,理论上 goroutine 之间的 stack 是相互独立的,只有在运行时可以执行这样的操作。
这么做纯粹是出于性能优化的考虑,原来的步骤是:
优化后,相当于减少了 G2 获取锁并且执行 memcopy 的性能消耗。
channel 设计背后的思想可以理解为 simplicity 和 performance 之间权衡抉择,具体如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的实现,使用锁的队列实现更简单,容易实现
Q2: Channel简易教程
不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程(goroutine)之间的通信来实现数据共享。这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制。
channel 可以看成一个 FIFO 队列,对 FIFO 队列的读写都是原子的操作,不需要加锁。对 channel 的操作行为结果总结如下:
读取一个已关闭的 channel 时,总是能读取到对应类型的零值,为了和读取非空未关闭 channel 的行为区别,可以使用两个接收值:
golang 中大部分类型都是值类型(只有 slice / channel / map 是引用类型),读/写类型是值类型的 channel 时, 如果元素 size 比较大时,应该使用指针代替,避免频繁的内存拷贝开销 。
main方法里创建了一个string类型的Channel,实现主协程与子协程 go SendMessage 进行通信。主协程执行到 -values 时发生阻塞,等待读取 values Channel的值,而子协程执行 SendMessage 方法时写入 values Channel。即,主协程发生阻塞,等待子协程执行完毕后再继续执行。
执行的结果如下:
从日志可以看出,打印val语句必须在子协程 go SendMessage 执行完成后才执行。
如果在此基础上添加多一个协程写入 values Channel会发生什么?
在主协程中,启动两个子协程给Channel写数据。而在 SendMessage 方法里,为了达到显示效果,在写入Channel前先睡眠1秒,在主协程也添加睡眠时间。
执行日志打印如下:
发现只有其中一个协程完成写入Channel的操作。因为此Channel没有设置缓存,所有只能保存一个写入值。
那么如何才能保证两个子协程能正常写入Channel呢?
为了保证上面的两个子协程能顺利地把值写入Channel,我们创建一个带缓冲的Channel。
新创建的Channel缓冲两个值,这样就能保证两个子协程能正常写入到Channel中。下面看看打印日志:
如我们猜想一样,两个子协程都能顺利结束。
晚安~
Q3: 用生产者消费者理解golang channel
生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品。
golang 的channel天生具有这种特性,即
①缓冲区满时写,缓冲区空时读,都会阻塞。
②channel 本身就是并发安全的。
golang实现多生产者多消费者:
运行结果:
可以看出,用golang实现生产者消费者非常简单,PV操作不需要各种加锁解锁,奥妙就在于CSP模型,即golang提倡的用通信代替共享内存。
Q4: Go CSP并发模型
Go的CSP并发模型
Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。
CSP 是 Communicating Sequential Process 的简称,中文可以叫做通信顺序进程,是一种并发编程模型,由 Tony Hoare 于 1977 年提出。简单来说,CSP 模型由并发执行的实体(线程或者进程)所组成,实体之间通过发送消息进行通信,这里发送消息时使用的就是通道,或者叫 channel。CSP 模型的关键是关注 channel,而不关注发送消息的实体。 Go 语言实现了 CSP 部分理论 。
“ 不要以共享内存的方式来通信,相反, 要通过通信来共享内存。”
Go的CSP并发模型,是通过 goroutine和channel 来实现的。
goroutine 是Go语言中并发的执行单位。其实就是协程。
channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。
Channel
Goroutine
Q5: go语言无缓冲的channel
无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
阻塞:由于某种原因数据没有到达,当前协程(线程)持续处于等待状态,直到条件满足,才接触阻塞。
同步:在两个或多个协程(线程)间,保持数据内容一致性的机制。
下图展示两个 goroutine 如何利用无缓冲的通道来共享一个值:
在第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执行发送或者接收。
在第 2 步,左侧的 goroutine 将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个 goroutine 会在通道中被锁住,直到交换完成。
在第 3 步,右侧的 goroutine 将它的手放入通道,这模拟了从通道里接收数据。这个 goroutine 一样也会在通道中被锁住,直到交换完成。
在第 4 步和第 5 步,进行交换,并最终,在第 6 步,两个 goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做别的事情了。
如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞到发送者准备好发送和接收者准备好接收。
无缓冲channel: —— 同步通信
关于go语言共享内存通信和go 内存共享的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。





