31|并发:Go的并发方案实现方案是怎样的?
31|并发:Go的并发方案实现方案是怎样的?
讲述:Tony Bai
时长17:45大小16.21M
什么是并发?
Go 的并发方案:goroutine
goroutine 的基本用法
goroutine 间的通信
小结
思考题
赞 38
提建议
精选留言(19)
- pythonbug2022-04-28老师好,有个问题想不大清楚:当main里面有go func的时候,是会将go func 放到另外一个处理器执行;还是说当前处理器先执行go func,然后一段时间后回来继续执行main,这样切换来切换去的。
作者回复: 第一, goroutine是轻量级线程(G),它被调度到执行器M上执行,而不是cpu上。M会被os调度到cpu上执行。 第二,可运行的G先被放入P的队列,每个P绑定一个可以用于执行G的M。之后调度程序可以从P队列中取出G放在M上执行。一个G执行一定时间后,再从队列中取出另一个G运行。 基于上述描述,你的问题,当main中通过go func启动一个新goroutine后,就会有两个可运行的(runnable)的G,新G会被放入P的队列并等待被调度。至于新G是否会与原先的main G分配到一个P上,不确定。如果机器只有一个cpu core。那么显然就如你所说的,新G与main G轮流被调度执行。如果有多个cpu core,那么新G与main G可能就是并行(paralell)执行的。
共 2 条评论15 - lesserror2022-01-08大白老师很擅长将复杂的知识深入浅出的讲解出来,这是很多教程没有做到的。读了这一篇,对于Go的并发设计有了新的认识,意犹未尽。另有两处模糊的地方: 1. 文中的 P-n、P-m,这里的n和m应该没有特别的含义吧? 就是指代一个Process而已吧? 2. 文中说:“比如涉及性能敏感的区域或需要保护的结构体数据时”,这里的:结构体数据,应该就是Go的 struct 吧?展开
作者回复: 1. 对,n,m只是序号。 2.对。通常我们用struct来抽象事物。保护的也是通常也是这种类型数据。
12 - 罗杰2022-01-08作为一个开发者,还是要尽早了解并发和并行的区别,这节课要好好的学习和理解。
作者回复: 👍
5 - 路边的猪2022-06-16有个问题请教下各位。 func main() { ctx, cancel := context.WithCancel(context.Background()) go func() { defer func() { fmt.Println("goroutine exit") }() for { select { case <-ctx.Done(): return default: time.Sleep(time.Second) } } }() time.Sleep(time.Second) cancel() time.Sleep(2 * time.Second) } 最近看并发原语这块。一个使用场景是通过cancelContext 来中止一个 执行慢业务的groutine。 有个问题不解,通过select 语句 监听两个 case 。 其中一个case 用于监听 ctx取消的,然后返回终止当前groutine执行。另一个case 用户执行慢业务逻辑。 这里问题是,这个监听的动作需要不停的去for循环 检查ctx.Done ,但是真正的慢业务 会阻塞 select啊。也就检查不到 ctx.Done啊,还怎么起到 通过ctx 控制取消慢业务groutine的作用呢? 比如这里的default语句 如果里面的业务不是睡眠1秒而是发起了一个网络调用需要很久,那即使 下面cancel() 被调用 select语句中依然会被阻塞在 网络调用里。展开
作者回复: 好问题! 我的理解是:那就需要那个慢业务调用本身也支持Context。很多人说context.Context有“传染”效应,大概就是这个意思。
共 2 条评论4 - 9月2022-06-12也想请问一下老师,能不能顺便讲解一下,同步和异步的概念
作者回复: 同步与异步的概念应用很广,在很多领域都有应用。比如通信领域,比如并发编程领域。 这一讲的同步设计中的“同步(sync)”指的是并发编程中对临界区进行“互斥”访问的概念,即有且仅有一个goroutine可以进入临界区,操作临界区的数据。其他goroutine只能在临界区外“排队”等待时机进入。 不过无论用在那个领域,“同步”与“异步”的通用含义是: 同步操作:执行流 只有等待发起的同步操作完成后,才能继续向下执行。 异步操作:发起异步操作后,原执行流可以无需等待,即可向下继续执行。
2 - 9月2022-06-12读了这节课,让我清晰认识到之前的的一个误区,就是并发和并行的区别。并发:针对的是程序结构设计,将一个程序分成若干个模块,不同模块单独执行,由多个模块相互交替执行,实现程序的运行。并行:针对的程序的执行,指的是同一时间点,有个多个任务在被多个或者多核的CPU下调度执行。
作者回复: 👍
共 2 条评论2 - 左耳朵东2022-11-04 来自北京可不可以这样理解:输入输出原语应用在函数上就是函数签名(参数、返回值),应用到 goroutine 之间就是 channel。具体一点,在函数场景想要输入马上想到通过参数传入,想要输出通过返回值给;在 goroutine 之间,想要输入则马上想到通过 channel 拿,想要把处理结果输出,放到 channel 中就行了。
作者回复: 有点这个意思。
1 - 菠萝吹雪—Code2022-09-06 来自北京这节讲的太好了,并发和并行理解透了
作者回复: 👍
1 - ivhong2022-03-11做了如下的联系,实现了等待锁和并发锁 type Lock struct { lock chan int } func NewLock() (l Lock) { l = Lock{make(chan int, 1)} l.lock <- 1 return } func (l *Lock) locked() { <-l.lock } func (l *Lock) unlocked() { l.lock <- 1 } type GroupLock struct { gGroup chan int lock chan int n int } func (gl *GroupLock) Set(n int) { gl.gGroup = make(chan int, n) gl.lock = make(chan int, n) gl.n = n for i := 0; i < n; i++ { gl.gGroup <- i } } func (gl *GroupLock) Done() { n := <- gl.gGroup gl.lock <- n } func (gl *GroupLock) Wait(){ i := 0 FOR:for { select { case <-gl.lock: i++ if i == gl.n { close(gl.gGroup) close(gl.lock) break FOR } } } } func main() { var gl GroupLock gl.Set(1000) p := 1 l := NewLock() for i := 0; i < 1000; i++ { go func(i int) { l.locked() p = p + 1 l.unlocked() gl.Done() }(i) } gl.Wait() fmt.Println(p) }展开
作者回复: 👍
1 - aoe2022-01-11CSP、响应式编程简介: CSP(Communicationing Sequential Processes,通信顺序进程)并发模型 响应式编程或反应式编程(英语:Reactive programming)是一种面向数据流和变化传播的声明式编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 CSP、响应式编程两者的思想非常像(以我目前的理解没看出区别),但是使用Java、Scala之类的语言实现响应式编程一般需要借助额外的框架编程(例如 Reactor)。 但使用 Go 编程居然是语言自带的特性,一个关键字 go 就行了!展开
作者回复: 👍
1 - Julien2022-01-11老师你好。如果一个goroutine需要执行很长时间,程序退出时希望它尽快退出,应该怎样做呢?
作者回复: 基本方法:基于channel通信。通过channel向那个goroutine发一个退出信号。后者接收到后主动退出。
共 3 条评论1 - 阿星2023-01-23 来自四川func spawn(f func() error) <-chan error { c := make(chan error) go func() { c <- f() }() return c } func main() { c := spawn(func() error { time.Sleep(2 * time.Second) return errors.New("timeout") }) fmt.Println("hello,world1") fmt.Println(<-c) fmt.Println("hello,world2") } 通过对上面的代码测试后我的理解是这样的: spawn中的 go func() 是异步执行(可能与主线程是并行), 所以在主线程中的spawn不会阻塞,先打印出了 "hello,world1", 等到打印 <-c 的时候就阻塞了,等从c 中读出数据打印结束后才会继续执行后续的打印 "hello,world2"。 所以说从channel 中读取数据可以认为肯定是一个阻塞的同步数据操作,不知道我的理解对吗?展开
作者回复: ✅👍。
- 张申傲2022-02-19老师讲的真好,并行和并发概念的区分很容易理解,传统编程语言和Go语言的并发设计模式之间的差异也很清晰。
作者回复: 👍。
- 小宝2022-02-14首先,讲解了并发与并行的概念: 简单讲并发关乎结构,并行关乎物理执行; 采用并发设计的程序,依旧不可以并行执行(单核CPU)。而在满足并行必要条件的情况下,采用并发设计的程序是可以并行执行的。而那些没有采用并发设计的应用程序,除非是启动多个程序实例,否则是无法并行执行的。 其次,对比了Go语言并发与其他语言并发的差异; 1. 底层实现; Go 并没有使用操作系统线程作为承载分解后的代码片段(模块)的基本执行单元,而是实现了goroutine这一由 Go 运行时(runtime)负责调度的、轻量的用户级线程,为并发程序设计提供原生支持。 2. 通信机制 传统语言的并发模型是基于对内存的共享的。 Go采用CSP(Communicationing Sequential Processes,通信顺序进程) 最后goroutine 的基本用法 go 关键字以及 channel展开
作者回复: 👍
- BinyGo2022-01-13老师讲的太好了,一发通透,牛!
作者回复: 👍
- Bynow2022-01-11大白老师的这篇讲义,怒赞!站在巨人的肩膀上果然是视野清晰!
作者回复: 👍
- return2022-01-10老师讲的太好了。 并发是应用结构设计相关的概念, 这个确实第一次看到,还需要自己好好理解。 输入输出,通过 channel 将 goroutine(P)组合连接在一起。 这个 一下子就 把哲学串联起来了。 比很多文章 单纯讲 channel是用来通信的, 更系统,更好领会。
作者回复: 👍
- LiWZ2022-01-08文章质量很高 谢谢老师👍
作者回复: 👍
- bearlu2022-01-07老师,设计并发的程序,不一定并行,那样存不存在一种并发需要并行才可以运行的程序?
作者回复: 并发是结构设计,是设计阶段考虑的问题。并发设计的程序即便是单核cpu也能运行。所以理论上只要是正确的并发设计,不应该必须依赖必须并行这样的条件。