You are on page 1of 48

浅谈并发编程

Paul
2022-01-19
ZHUMANG TECHNOLOGY

并发与并行

2
ZHUMANG TECHNOLOGY

Joe Armstrong

Concurrent and Parallel Programming - Joe Armstrong - Erlang and other stuff
3
ZHUMANG TECHNOLOGY

Rob Pike
Concurrency vs. parallelism
Concurrency is about dealing with lots of things at once.
Parallelism is about doing lots of things at once.
Not the same, but related.
Concurrency is about structure, parallelism is about execution.
Concurrency provides a way to structure a solution to solve a problem that may (but not
necessarily) be parallelizable.
Concurrency is not Parallelism (go.dev)
4
ZHUMANG TECHNOLOGY

5
ZHUMANG TECHNOLOGY

6
ZHUMANG TECHNOLOGY

7
ZHUMANG TECHNOLOGY

使用甘特图类比

8
ZHUMANG TECHNOLOGY

并发模型
线程与锁
Actor
CSP

9
ZHUMANG TECHNOLOGY

线程与锁机制
以 Java 为例

10
ZHUMANG TECHNOLOGY
可能的输出?
public class PossibleReordering {

static int x = 0, y = 0;

static int a = 0, b = 0;

public static void main(String[] args) throws InterruptedException {

Thread one = new Thread(new Runnable() {

public void run() {

a = 1;

x = b;

});

Thread other = new Thread(new Runnable() {


public void run() {

b = 1;

y = a;

});

one.start();other.start();

one.join();other.join();

System.out.println("(" + x + "," + y + ")");

11
ZHUMANG TECHNOLOGY

诡异的内存

12
ZHUMANG TECHNOLOGY

public class Puzzle {

static boolean answerReady = false;

static int answer = 0;

static Thread t1 = new Thread() {

public void run() {

answer = 42;

answerReady = true;

};

static Thread t2 = new Thread() {

public void run() {

if (answerReady)

System.out.println("The meaning of life is: " + answer);

else

System.out.println("I don't know the answer");

};

public static void main(String[] args) throws InterruptedException {

t1.start(); t2.start();

t1.join(); t2.join();
}

13
ZHUMANG TECHNOLOGY

The meaning of life is 42

or
I don't know the answer

14
ZHUMANG TECHNOLOGY

The meaning of life is 42

or
I don't know the answer

竟态

15
ZHUMANG TECHNOLOGY

The meaning of life is 42

or
I don't know the answer

or
?

16
ZHUMANG TECHNOLOGY

The meaning of life is 42

or
I don't know the answer

or
The meaning of life is: 0

17
ZHUMANG TECHNOLOGY

诡异的内存 —— 乱序执行
编译器的静态优化可以打乱代码的执行顺序
JVM的动态优化也会打乱代码的执行顺序
硬件可以通过乱序执行来优化其性能

18
ZHUMANG TECHNOLOGY

可以更糟糕
public void run() {

while (!answerReady) {

Thread.Sleep(100);

System.out.println("The meaning of life is: " + answer);

共享变量的修改在线程之间不可见,线程因读取的 answerReady 变量永远是false,造


成死循环

19
ZHUMANG TECHNOLOGY

解决竟态条件
线程间的同步 —— 锁

20
ZHUMANG TECHNOLOGY

并发不安全
class Counter {

private int count = 0;

public void increment() {

++count;

public int getCount() {

return count;

21
ZHUMANG TECHNOLOGY

改进
class Counter {

private int count = 0;

public synchronized void increment() {

++count;

public int getCount() {

return count;

22
ZHUMANG TECHNOLOGY

还不够
class Counter {

private int count = 0;

public synchronized void increment() {

++count;

public synchronized int getCount() {

return count;

23
ZHUMANG TECHNOLOGY

更好的代码
final AtomicInteger counter = new AtomicInteger();

class CountingThread extends Thread {


public void run() {

for(int x = 0; x < 10000; ++x)

counter.incrementAndGet();

24
ZHUMANG TECHNOLOGY

锁与死锁
哲学家进餐问题
一张圆桌
五个哲学家
五支筷子
哲学家放下筷子思考问题,或拿
起左右两支筷子进餐

25
ZHUMANG TECHNOLOGY
初始版本(存在死锁)
class Philosopher extends Thread {

private Chopstick left, right;

private Random random;

private int thinkCount;

public Philosopher(Chopstick left, Chopstick right) {

this.left = left; this.right = right;

random = new Random();

public void run() {

try {

while(true) {

++thinkCount;

if (thinkCount % 10 == 0)

System.out.println("Philosopher " + this + " has thought " + thinkCount + " times");

Thread.sleep(random.nextInt(1000)); // Think for a while

synchronized(left) { // Grab left chopstick

synchronized(right) { // Grab right chopstick

Thread.sleep(random.nextInt(1000)); // Eat for a while

} catch(InterruptedException e) {}

26
ZHUMANG TECHNOLOGY
改进(按资源ID顺序加锁)
class Philosopher extends Thread {

private Chopstick first, second;

private Random random;

private int thinkCount;

public Philosopher(Chopstick left, Chopstick right) {

if(left.getId() < right.getId()) {

first = left; second = right;

} else {

first = right; second = left;

random = new Random();

public void run() {

try {

while(true) {

++thinkCount;

if (thinkCount % 10 == 0)

System.out.println("Philosopher " + this + " has thought " + thinkCount + " times");

Thread.sleep(random.nextInt(1000)); // Think for a while

synchronized(first) { // Grab first chopstick

synchronized(second) { // Grab second chopstick

Thread.sleep(random.nextInt(1000)); // Eat for a while

} catch(InterruptedException e) {}

27
ZHUMANG TECHNOLOGY
为锁增加超时(并没有杜绝死锁)
class Philosopher extends Thread {

private ReentrantLock leftChopstick, rightChopstick;

private Random random;

private int thinkCount;

public Philosopher(ReentrantLock leftChopstick, ReentrantLock rightChopstick) {

this.leftChopstick = leftChopstick; this.rightChopstick = rightChopstick;

random = new Random();

public void run() {

try {

while(true) {

++thinkCount;

if (thinkCount % 10 == 0)

System.out.println("Philosopher " + this + " has thought " + thinkCount + " times");

Thread.sleep(random.nextInt(1000)); // Think for a while

leftChopstick.lock();

try {

if (rightChopstick.tryLock(1000, TimeUnit.MILLISECONDS)) { // Got the right chopstick

try {

Thread.sleep(random.nextInt(1000)); // Eat for a while

} finally { rightChopstick.unlock(); }

} else { // Didn't get the right chopstick - give up and go back to thinking

System.out.println("Philosopher " + this + " timed out");

} finally { leftChopstick.unlock(); }

} catch(InterruptedException e) {}

28
ZHUMANG TECHNOLOGY
条件变量(只有一个锁)
public Philosopher(ReentrantLock table) {

eating = false;

this.table = table;

condition = table.newCondition();

random = new Random();

public void setLeft(Philosopher left) {

this.left = left;

public void setRight(Philosopher right) {

this.right = right;

public void run() {

try {

while (true) {

think();

eat();

} catch (InterruptedException e) {

29
ZHUMANG TECHNOLOGY

条件变量(只有一个锁)
private void think() throws InterruptedException {

table.lock();

try {

eating = false;

left.condition.signal();

right.condition.signal();

} finally { table.unlock(); }

++thinkCount;

if (thinkCount % 10 == 0)

System.out.println("Philosopher " + this + " has thought " + thinkCount + " times");

Thread.sleep(1000);

private void eat() throws InterruptedException {

table.lock();

try {

while (left.eating || right.eating)

condition.await();

eating = true;

} finally { table.unlock(); }

Thread.sleep(1000);

30
ZHUMANG TECHNOLOGY

Java 中的基础设施
synchronized

java.util.concurrent
java.util.concurrent.atomic.AtomicInteger

java.util.concurrent.locks.ReentrantLock

java.util.concurrent.locks.Condition

...

31
ZHUMANG TECHNOLOGY

线程与锁模型优缺点
优点
适用面广,线程与锁更接近现实中资源使用的“本质”
可以解决不同颗粒度的问题
易于集成到指令式或面向对象语言
缺点
没有为并行提供直接支持,利用该模型可以将程序改为并发模式
仅支持单机,不支持分布式
难以测试,错误不易发现,不易复现
难以维护
32
ZHUMANG TECHNOLOGY

Actor
Erlang
Elixir

33
ZHUMANG TECHNOLOGY

Hello world
import :timer, only: [sleep: 1]

# 定义模块和方法

defmodule Talker do # 模块

def loop do # 函数

receive do

{:greet, name} -> IO.puts("Hello #{name}") #模式匹配 关键字 :greet

{:greet, name, type} -> IO.puts("Good #{type}, #{name}") #关键词同为 :greet,参数数量不同

{:praise, name} -> IO.puts("#{name}, you're amazing")

{:celebrate, name, age} -> IO.puts("Here's to another #{age} year, #{name}")

end

loop() # 尾递归,会被优化为简单跳转, 、
erlang elixir没有循环结构

end

end

34
ZHUMANG TECHNOLOGY

Hello world
# 创建actor
实例
pid = spawn(&Talker.loop/0)

send(pid, {:greet, "Huey"})

send(pid, {:greet, "Huey", "morning"})

send(pid, {:praise, "Dewey"})

send(pid, {:celebrate, "Louie", 16})

send(pid, {:greet, "Huey", 16}) #无效,没有模式匹配

send(pid, {:hello, "Huey"})

sleep(1000)

35
ZHUMANG TECHNOLOGY

mailbox
队列式信箱

36
ZHUMANG TECHNOLOGY
actor 退出与通知
defmodule Talker do

def loop do

receive do

{:greet, name} -> IO.puts("Hello #{name}")

{:praise, name} -> IO.puts("#{name}, you're amazing")

{:celebrate, name, age} -> IO.puts("Here's to another #{age} years, #{name}")

#{:shutdown} -> exit(:normal)

{:shutdown} -> exit(:ByMe)

end

loop()

end

end

Process.flag(:trap_exit, true)

pid = spawn_link(&Talker.loop/0)

send(pid, {:greet, "Huey"})

send(pid, {:praise, "Dewey"})

send(pid, {:celebrate, "Louie", 16})

send(pid, {:shutdown})

receive do

{:EXIT, ^pid, reason} -> IO.puts("Talker has exited (#{reason})")

end

37
ZHUMANG TECHNOLOGY
actor 增加状态
defmodule Counter do

def start(count) do

spawn(__MODULE__, :loop, [count])

end

def next(counter) do

send(counter, {:next})

end

def loop(count) do

receive do

{:next} ->

IO.puts("Current count: #{count}")

loop(count + 1)

end

end

end

使用REPL工具 iex , counter = Counter.start(10) , send(count, {:next}) ,


Counter.next(counter)
38
ZHUMANG TECHNOLOGY
actor 之间通信
defmodule Counter do

def start(count) do

spawn(__MODULE__, :loop, [count])

end

def next(counter) do

ref = make_ref() # 唯一引用,类似 ,可用于比较

uuid
send(counter, {:next, self(), ref})

receive do

{:ok, ^ref, count} -> count

end

end

def loop(count) do

receive do

{:next, sender, ref} ->

send(sender, {:ok, ref, count})

loop(count + 1)

end

end

end

39
ZHUMANG TECHNOLOGY
actor 进程命名
defmodule Counter do

def start(count) do

pid = spawn(__MODULE__, :loop, [count])

Process.register(pid, :counter)

pid

end

def next do

ref = make_ref()

send(:counter, {:next, self(), ref})

receive do

{:ok, ^ref, count} -> count

end

end

def loop(count) do

receive do

{:next, sender, ref} ->

send(sender, {:ok, ref, count})

loop(count + 1)

end

end

end

40
ZHUMANG TECHNOLOGY

actor 进程命名
> iex

iex(3)> Process.whereis(:counter)

nil

iex(4)> pid = Counter.start(123)

#PID<0.134.0>

iex(5)> c = Process.whereis(:counter)

#PID<0.134.0>

iex(10)> Counter.next

10

41
ZHUMANG TECHNOLOGY

actor 并发
defmodule Parallel do

def map(collection, fun) do

parent = self()

processes = Enum.map(collection, fn(e) ->

spawn_link(fn() ->

send(parent, {self(), fun.(e)})

end)

end)

Enum.map(processes, fn(pid) ->

receive do

{^pid, result} -> result

end

end)

end

end

42
ZHUMANG TECHNOLOGY

actor 并发
> iex

iex(2)> slow_double = fn(x) -> :timer.sleep(1000);x*2 end

#Function<44.65746770/1 in :erl_eval.expr/5>

iex(3)> :timer.tc(fn() -> Enum.map([1, 2, 3, 4], slow_double) end)

{4006986, [2, 4, 6, 8]}

iex(4)> :timer.tc(fn() -> Parallel.map([1, 2, 3, 4], slow_double) end)

{1000908, [2, 4, 6, 8]}

43
ZHUMANG TECHNOLOGY

支持分布式 OTP
OTP ( Open Telecom Platform ) 库
支持完善的线程奔溃重启策略,包括重启频率
支持调试与日志功能
代码热升级,不需要停止整个系统
支持发布管理、故障切换、自动扩容等特性

44
ZHUMANG TECHNOLOGY

actor 模型的优缺点
优点
消息流而不是共享状态
actor事件串行执行
容错,“任其崩溃”哲学
天生分布式
缺点
仍然存在锁的问题
信箱溢出
45
ZHUMANG TECHNOLOGY

CSP
Communicating Sequential Processes
通信顺序进程
Golang

46
ZHUMANG TECHNOLOGY

面向对象的本质
Smalltalk 设计者、面向对象编程之父 Alan Kay:
很久以前,我在描述 ”面向对象编程“ 时使用了 “对象” 这个概念。很抱歉这个概念让
许多人误入歧途,他们将学习的重心放在了 “对象” 这个次要的方面。
真正主要的方面是 “消息” ……日文中有一个词ma,表示 “间隔”,与其最为相近的英
文或许是 “interstitial”。创建一个规模宏大且可生长的系统的关键在于其模块之间应
该如何交流,而不在于其内部的属性和行为应该如何表现。

47
ZHUMANG TECHNOLOGY

声明
本文档多数内容基于技术书籍《七周
七并发模型》,文档中的代码只作为
技术分享使用,如需个人或商业使
用,请咨询原作者,使用时务必经过
测试,本人不能为以上程序的正确性
提供保证!

48

You might also like