Skip to content

Commit

Permalink
并发
Browse files Browse the repository at this point in the history
  • Loading branch information
hekuangsheng committed Apr 20, 2022
1 parent dd4a166 commit 18f9499
Showing 1 changed file with 37 additions and 15 deletions.
52 changes: 37 additions & 15 deletions golang_CSP.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!--
* @Author: your name
* @Date: 2022-04-19 10:27:41
* @LastEditTime: 2022-04-20 20:47:58
* @LastEditTime: 2022-04-20 21:15:56
* @LastEditors: Please set LastEditors
* @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%A
* @FilePath: /golang-base/golang_CSP.md
Expand All @@ -18,12 +18,12 @@
在现代化身中,通道的概念成为一流的,这样做为我们提供了我们所寻求的间接性和独立性
通道的一个关键特征是阻塞。在最原始的形式中,无缓冲通道充当会合点,任何读取器都将等待写入器,反之亦然。可以引入缓冲,但不鼓励无界缓冲,因为带阻塞的有界缓冲可能是协调起搏和背压的重要工具,确保系统不会承担超出其能力的工作。

# 并发演进
# 并发问题
[并发之痛 Thread,Goroutine,Actor](http://jolestar.com/parallel-programming-model-thread-goroutine-actor/)
1. 竞态条件(race conditions) 如果每个任务都是独立的,不需要共享任何资源,那线程也就非常简单。但世界往往是复杂的
2. 依赖关系以及执行顺序 如果线程之间的任务有依赖关系,需要等待以及通知机制来进行协调

基于Java扩展
基于Java扩展来讲一下
> 内存(线程的栈空间):每个线程都需要一个栈(Stack)空间来保存挂起(suspending)时的状态。Java的栈空间(64位VM)默认是1024k,不算别的内存,只是栈空间,启动1024个线程就要1G内存。
> 调度成本(context-switch):国外一篇论文专门分析线程切换的成本,基本上得出的结论是切换成本和栈空间使用大小直接相关。
> CPU使用率:想提高CPU利用率,最大限度的压榨硬件资源,从这个角度考虑,我们应该用多少线程呢?没有固定答案,因为网络的时间不是固定的,另外比如锁,比如数据库连接池,就会更复杂。
Expand All @@ -32,7 +32,7 @@
1. 线程的成本较高(内存,调度)不可能大规模创建
2. 应该由语言或者框架动态解决这个问题

## Actor CSP
# 并发方案
Actor模型非常适用于多个组件独立工作,相互之间仅仅依靠消息传递的情况。如果想在多个组件之间维持一致的状态
1. 线程池方案
Java1.5后,Doug Lea的Executor系列被包含在默认的JDK内,是典型的线程池方案
Expand All @@ -42,15 +42,26 @@ Java1.5后,Doug Lea的Executor系列被包含在默认的JDK内,是典型的
2. 异步回调方案、GreenThread/Coroutine/Fiber方案(也就是大家常说的协程)
为了解决回调方法带来的难题,这种方案的思路是写代码的时候还是按顺序写,但遇到IO等阻塞调用时,将当前的代码片段暂停,保存上下文,让出当前线程。等IO事件回来,然后再找个线程让当前代码片段恢复上下文继续执行,写代码的时候感觉好像是同步的,仿佛在同一个线程完成的,但实际上系统可能切换了线程,但对程序无感。(全都在用户态)

> **缺点**: 从一个线程切换到另一个线程需要完整的上下文切换。因为可能需要多次内存访问,索引这个切换上下文的操作开销较大,会增加运行的cpu周期。
> **共性**: 从一个线程切换到另一个线程需要完整的上下文切换。因为可能需要多次内存访问,索引这个切换上下文的操作开销较大,会增加运行的cpu周期。GreenThread的理念就是异步回调在用户空间调度
3. Goroutine
内置了一个调度器,实现了Coroutine的多线程并行调度,同时通过对网络等库的封装,对用户屏蔽了调度细节。提供了Channel机制,用于Goroutine之间通信,实现CSP并发模型(Communicating Sequential Processes)。因为Go的Channel是通过语法关键词提供的,对用户屏蔽了许多细节。其实Go的Channel和Java中的SynchronousQueue是一样的机制,如果有buffer其实就是ArrayBlockQueue

> **好处**: 区别于操作系统内核调度操作系统线程,goroutine 的调度是Go语言运行时(runtime)层面的实现,是完全由 Go 语言本身实现的一套调度系统——go scheduler。它的作用是按照一定的规则将所有的 goroutine 调度到操作系统线程上执行。下面[Golang Goroutine](#golang-goroutine)会有介绍
> 单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的, goroutine 则是由Go运行时(runtime)自己的调度器调度的,完全是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多
## Java Akka
单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的, goroutine 则是由Go运行时(runtime)自己的调度器调度的,完全是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多

但是:池子里设置多少个Goroutine合适?
所以这个问题还是没有从更本上解决。

## 并发之-actor模型
actor的目标:
Actor可独立更新,实现热升级
无缝弥合本地和远程调用
容错 Actor之间的通信是异步的,发送方只管发送,不关心超时以及错误
易扩展,天然分布式 因为Actor的通信机制弥合了本地和远程调用
**这他妈不就是MQ???go里面netchan,实现远程Channel(但是废弃)**

* Akka(Scala,Java)基于线程和异步回调模式实现
* 要么是Akka提供的异步框架,要么通过Future-callback机制,转换成回调模式
* Quasar (Java) 为了解决Akka的阻塞回调问题,Quasar通过字节码增强的方式,在Java中实现了Coroutine/Fiber
Expand All @@ -60,13 +71,6 @@ Java1.5后,Doug Lea的Executor系列被包含在默认的JDK内,是典型的
* Go实现了M:N的调度,Goroutine任务执行,相当于一种rebalance机制
* 系统启动时,会启动一个独立的后台线程空闲挂起[runtime.gopark],忙碌调出[将event中的pollDesc取出来,找到关联的阻塞Goroutine]

### Goroutine优缺点
1. Go通过Goroutine的调度解决了CPU利用率的问题,goroutine 被 Go runtime 所调度,这一点和线程不一样。也就是说,Go 语言的并发是由 Go 自己所调度的,自己决定同时执行多少个 goroutine,什么时候执行哪几个。这些对于我们开发者来说完全透明,只需要在编码的时候告诉 Go 语言要启动几个 goroutine,至于如何调度执行,我们不用关心

2. 操作系统的线程一般都有固定的栈内存(通常为2MB),而 Go 语言中的 goroutine 非常轻量级,一个 goroutine 的初始栈空间很小(一般为2KB),所以在 Go 语言中一次创建数万个 goroutine 也是可能的。并且 goroutine 的栈不是固定的,可以根据需要动态地增大或缩小, Go 的 runtime 会自动为 goroutine 分配合适的栈空间

3. 互联网在线应用场景下,如果每个请求都扔到一个Goroutine里,当资源出现瓶颈的时候,会导致大量的Goroutine阻塞,最后用户请求超时。(比如带锁的共享资源,比如数据库连接等。这时候就需要用Goroutine池来进行控流)

### Goroutine调度机制
![gpm](pic/gpm.png)
- G:表示 goroutine,每执行一次go f()就创建一个 G,包含要执行的函数和上下文信息
Expand All @@ -76,6 +80,13 @@ Java1.5后,Doug Lea的Executor系列被包含在默认的JDK内,是典型的
- M:线程想运行任务就得获取 P,从 P 的本地队列获取 G,当 P 的本地队列为空时,M 也会尝试从全局队列或其他 P 的本地队列获取 G。M 运行 G,G 执行之后,M 会从 P 获取下一个 G,不断重复下去
- Goroutine 调度器和操作系统调度器是通过 M 结合起来的,每个 M 都代表了1个内核线程,操作系统调度器负责把内核线程分配到 CPU 的核上执行

### Goroutine优缺点
1. Go通过Goroutine的调度解决了CPU利用率的问题,goroutine 被 Go runtime 所调度,这一点和线程不一样。也就是说,Go 语言的并发是由 Go 自己所调度的,自己决定同时执行多少个 goroutine,什么时候执行哪几个。这些对于我们开发者来说完全透明,只需要在编码的时候告诉 Go 语言要启动几个 goroutine,至于如何调度执行,我们不用关心

2. 操作系统的线程一般都有固定的栈内存(通常为2MB),而 Go 语言中的 goroutine 非常轻量级,一个 goroutine 的初始栈空间很小(一般为2KB),所以在 Go 语言中一次创建数万个 goroutine 也是可能的。并且 goroutine 的栈不是固定的,可以根据需要动态地增大或缩小, Go 的 runtime 会自动为 goroutine 分配合适的栈空间

3. 互联网在线应用场景下,如果每个请求都扔到一个Goroutine里,当资源出现瓶颈的时候,会导致大量的Goroutine阻塞,最后用户请求超时。(比如带锁的共享资源,比如数据库连接等。这时候就需要用Goroutine池来进行控流)

## Golang CSP VS Actor
1. CSP模型里消息和Channel是主体,处理器是匿名的(channel与数据类型绑定)

Expand All @@ -88,6 +99,7 @@ go里面有阻塞式和非阻塞式两种:

2. Actor模型里Actor是主体,Mailbox(类似于CSP的Channel)是透明的(队列与类型不强相关)

3. [go里面的Actor实现](https://www.jianshu.com/p/b2d2a3d72e9f) [github开源项目:protoactor-go](https://github.com/asynkron/protoactor-go)
---
# 补充
## Channel
Expand Down Expand Up @@ -119,4 +131,14 @@ Context 就是用来简化解决这些问题的,并且是并发安全的。Con
## Race
* [race](https://github.com/singgel/golang-base/blob/main/sync_race/main.go)
Go语言中单元测试的时候加上-race参数,可以实现并发测试,但 race-enabled 程序耗费的 CPU 和内存通常是正常程序的十倍,在真实环境下一直启用竞态检测是非常不切合实际的
[issues/3970](https://github.com/golang/go/issues/3970)这个官方源码案例告诉我们:它不会发出假的提示,认真严肃地对待它的每条警示非常必要。但它并非万能,还是需要以你对并发特性的正确理解为前提,才能真正地发挥出它的价值
[issues/3970](https://github.com/golang/go/issues/3970)这个官方源码案例告诉我们:它不会发出假的提示,认真严肃地对待它的每条警示非常必要。但它并非万能,还是需要以你对并发特性的正确理解为前提,才能真正地发挥出它的价值

---
# 题外话
## Rust
Rust解决并发问题的思路是首先承认现实世界的资源总是有限的,想彻底避免资源共享是很难的,不试图完全避免资源共享,它认为并发的问题不在于资源共享,而在于错误的使用资源共享
大多数语言定义类型的时候,并不能限制调用方如何使用,只能通过文档或者标记的方式(比如Java中的@ThreadSafe ,@NotThreadSafe annotation)说明是否并发安全,但也只能仅仅做到提示的作用,不能阻止调用方误用
而Rust:
1. 定义类型的时候要明确指定该类型是否是并发安全的
2. 引入了变量的所有权(Ownership)概念 非并发安全的数据结构在多个线程间转移,也不一定就会导致问题,导致问题的是多个线程同时操作,也就是说是因为这个变量的所有权不明确导致的
有了所有权的概念后,变量只能由拥有所有权的作用域代码操作,而变量传递会导致所有权变更,从语言层面限制了竞态条件出现的情况。

0 comments on commit 18f9499

Please sign in to comment.