沧海一粟

天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。

0%

并发模型

(1) Actor

Actor模型:面向对象原生的并发模型

Actor模型是高性能网络中处理并行任务的一种方法,解决并发问题的利器

Actor模型本质上是一种计算模型,基本的计算单元称为 Actor,在 Actor 模型里,一切都是 Actor,所有的计算都是在 Actor 中执行的,并且 Actor 之间是完全隔离的,不会共享任何变量。

Actor模型解决了 传统编程假设与现代多线程、多CPU架构的现实之间的不匹配问题。

  1. 消息传递的使用避免了锁和阻塞。
  2. 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”) 这一行代码就搞定了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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