并发模型
(1) Actor
Actor模型:面向对象原生的并发模型
Actor模型是高性能网络中处理并行任务的一种方法,解决并发问题的利器
Actor模型本质上是一种计算模型,基本的计算单元称为 Actor,在 Actor 模型里,一切都是 Actor,所有的计算都是在 Actor 中执行的,并且 Actor 之间是完全隔离的,不会共享任何变量。
Actor模型解决了 传统编程假设与现代多线程、多CPU架构的现实之间的不匹配问题。
- 消息传递的使用避免了锁和阻塞。
- Actor能够优化地处理错误情况。
Java 语言本身并不支持 Actor 模型,所以如果你想在 Java 语言里使用 Actor 模型,就需要借助第三方类库,目前能完备地支持 Actor 模型而且比较成熟的类库就是 Akka。
(2) STM
软件事务内存(Software Transactional Memory,简称 STM)
传统的数据库事务,支持 4 个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),也就是大家常说的 ACID
STM 由于不涉及到持久化,所以只支持 ACI。
Multiverse
software-transactional-memory-in-scala
stm-java
(3) 协程
我们可以把协程简单地理解为一种轻量级的线程。从操作系统的角度来看,线程是在内核态中调度的,而协程是在用户态调度的,所以相对于线程来说,协程切换的成本更低。协程虽然也有自己的栈,但是相比线程栈要小得多,典型的线程栈大小差不多有 1M,而协程栈的大小往往只有几 K 或者几十 K。所以,无论是从时间维度还是空间维度来看,协程都比线程轻量得多。
支持协程的语言还是挺多的,例如 Golang、Python、Lua、Kotlin 等都支持协程。
(3.1) golang中的协程
在 Golang 中创建协程非常简单,在下面的示例代码中,要让 hello() 方法在一个新的协程中执行,只需要go hello(“World”) 这一行代码就搞定了。
import (
"fmt"
"time"
)
func hello(msg string) {
fmt.Println("Hello " + msg)
}
func main() {
//在新的协程中执行hello方法
go hello("World")
fmt.Println("Run in main")
//等待100毫秒让协程执行结束
time.Sleep(100 * time.Millisecond)
}
(4) CSP模型
CSP(Communicating Sequential Processes)
打印从 1 累加到 100 亿的结果,如果使用单个协程来计算,大概需要 4 秒多的时间。单个协程,只能用到 CPU 中的一个核,为了提高计算性能,我们可以用多个协程来并行计算,这样就能发挥多核的优势了。
用了 4 个子协程来并行执行,这 4 个子协程分别计算[1, 25 亿]、(25 亿, 50 亿]、(50 亿, 75 亿]、(75 亿, 100 亿],最后再在主协程中汇总 4 个子协程的计算结果。主协程要汇总 4 个子协程的计算结果,势必要和 4 个子协程之间通信,Golang 中协程之间通信推荐的是使用 channel,channel 你可以形象地理解为现实世界里的管道。
import (
"fmt"
"time"
)
func main() {
// 变量声明
var result, i uint64
// 单个协程执行累加操作
start := time.Now()
for i = 1; i <= 10000000000; i++ {
result += i
}
// 统计计算耗时
elapsed := time.Since(start)
fmt.Printf("执行消耗的时间为:", elapsed)
fmt.Println(", result:", result)
// 4个协程共同执行累加操作
start = time.Now()
ch1 := calc(1, 2500000000)
ch2 := calc(2500000001, 5000000000)
ch3 := calc(5000000001, 7500000000)
ch4 := calc(7500000001, 10000000000)
// 汇总4个协程的累加结果
result = <-ch1 + <-ch2 + <-ch3 + <-ch4
// 统计计算耗时
elapsed = time.Since(start)
fmt.Printf("执行消耗的时间为:", elapsed)
fmt.Println(", result:", result)
}
// 在协程中异步执行累加操作,累加结果通过channel传递
func calc(from uint64, to uint64) <-chan uint64 {
// channel用于协程间的通信
ch := make(chan uint64)
// 在协程中执行累加操作
go func() {
result := from
for i := from + 1; i <= to; i++ {
result += i
}
// 将结果写入channel
ch <- result
}()
// 返回结果是用于通信的channel
return ch
}
References
[1] 42 | Actor模型:面向对象原生的并发模型
[2] 43 | 软件事务内存:借鉴数据库的并发经验
[3] 44 | 协程:更轻量级的线程
[4] 45 | CSP模型:Golang的主力队员
[5] doc.akka.io/docs/akka/2.2/AkkaJava.pdf
[6] guides/akka-quickstart-java
[7] akka-quickstart-java
[8] doc.akka.io/docs/akka/current
[9] akka-guide
[10] stm-java
[11] software-transactional-memory-in-scala
[12] notes-on-structured-concurrency-or-go-statement-considered-harmful