并发编程基础知识一 并发和并行

这里先放结论:你可以编写一个拥有多个线程或者进程的并发程序,但如果没有多核处理器来执行这个程序,那么就不能以并行方式来运行代码。并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并行,而并发是多个线程被(一个)cpu 轮流切换着执行。

参考
详解并发和并行意义
并发与并行的区别?

一、例一

假设一个有三个学生需要辅导作业,帮每个学生辅导完作业是一个任务

1.顺序执行:老师甲先帮学生A辅导,辅导完之后再取给B辅导,最后再去给C辅导,效率低下 ,很久才完成三个任务

2.并发:老师甲先给学生A去讲思路,A听懂了自己书写过程并且检查,而甲老师在这期间直接去给B讲思路,讲完思路再去给C讲思路,让B自己整理步骤。这样老师就没有空着,一直在做事情,很快就完成了三个任务。与顺序执行不同的是,顺序执行中老师讲完思路之后学生在写步骤,这在这期间,老师是完全空着的,没做事的,所以效率低下。

3.并行:直接让三个老师甲、乙、丙三个老师“同时”给三个学生辅导作业,也完成的很快。

二、例二

1.顺序执行:你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。

2.并发:并发应该是一手筷子,一手电话,说一句话,咽一口饭。

3.并行是咽一口饭同时说一句话,而这光靠一张嘴是办不到的,至少两张嘴。

三、理解

1.并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。

2.并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。

3.在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群

普通解释:
并发:交替做不同事情的能力
并行:同时做不同事情的能力
专业术语:
并发:不同的代码块交替执行
并行:不同的代码块同时执行

并发和并行的意义:
并发和并行都可以处理“多任务”,二者的主要区别在于是否是“同时进行”多个的任务。

四、摘自:《并发的艺术》 — 〔美〕布雷谢斯

如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。并发系统与并行系统这两个定义之间的关键差异在于“存在”这个词。

在并发程序中可以同时拥有两个或者多个线程。这意味着,如果程序在单核处理器上运行,那么这两个线程将交替地换入或者换出内存。这些线程是同时“存在”的——每个线程都处于执行过程中的某个状态。如果程序能够并行执行,那么就一定是运行在多核处理器上。此时,程序中的每个线程都将分配到一个独立的处理器核上,因此可以同时运行。

我相信你已经能够得出结论——“并行”概念是“并发”概念的一个子集。也就是说,你可以编写一个拥有多个线程或者进程的并发程序,但如果没有多核处理器来执行这个程序,那么就不能以并行方式来运行代码。因此,凡是在求解单个问题时涉及多个执行流程的编程模式或者执行行为,都属于并发编程的范畴。

并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并行,而并发是多个线程被(一个)cpu 轮流切换着执行。

五、摘自《go语言实战》P125

并发(concurrency)不是并行(parallelism)。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。在很多情况下,并发的效果比并行好,因为操作系统和硬件的总资源一般很少,但能支持系统同时做很多事情。这种“使用较少的资源做更多的事情” 的哲学,也是指导 Go 语言设计的哲学。

如果希望让 goroutine 并行,必须使用多于一个逻辑处理器。 当有多个逻辑处理器时,调度器会将 goroutine 平等分配到每个逻辑处理器上。这会让 goroutine 在不同的线程上运行。不过要想真的实现并行的效果,用户需要让自己的程序运行在有多个物理处理器的机器上。否则,哪怕 Go 语言运行时使用多个线程, goroutine 依然会在同一个物理处理器上并发运行,达不到并行的效果。

图 6-3展示了在一个逻辑处理器上并发运行 goroutine和在两个逻辑处理器上并行运行两个并发的 goroutine 之间的区别。调度器包含一些聪明的算法,这些算法会随着 Go语言的发布被更新和改进,所以不推荐盲目修改语言运行时对逻辑处理器的默认设置。如果真的认为修改逻辑处理器的数量可以改进性能,也可以对语言运行时的参数进行细微调整。后面会介绍如何做这种修改。


image.png
12 func main() {
13 // 分配一个逻辑处理器给调度器使用
14 runtime.GOMAXPROCS(1)
15
16 // wg 用来等待程序完成
17 // 计数加 2,表示要等待两个 goroutine
18 var wg sync.WaitGroup
19 wg.Add(2)
20
21 fmt.Println("Start Goroutines")
22
23 // 声明一个匿名函数,并创建一个 goroutine
24 go func() {
25 // 在函数退出时调用 Done 来通知 main 函数工作已经完成
26 defer wg.Done()
27
28 // 显示字母表 3 次
29 for count := 0; count < 3; count++ {
30 for char := 'a'; char < 'a'+26; char++ {
31 fmt.Printf("%c ", char)
32 }
33 }
34 }()
35
36 // 声明一个匿名函数,并创建一个 goroutine
37 go func() {
38 // 在函数退出时调用 Done 来通知 main 函数工作已经完成
39 defer wg.Done()
40
41 // 显示字母表 3 次
42 for count := 0; count < 3; count++ {
43 for char := 'A'; char < 'A'+26; char++ {
44 fmt.Printf("%c ", char)
45 }
46 }
47 }()
48
49 // 等待 goroutine 结束
50 fmt.Println("Waiting To Finish")
51 wg.Wait()
52
53 fmt.Println("\nTerminating Program")
54 }

在代码清单 6-1 的第 14 行,调用了 runtime 包的 GOMAXPROCS 函数。这个函数允许程序更改调度器可以使用的逻辑处理器的数量。如果不想在代码里做这个调用,也可以通过修改和这个函数名字一样的环境变量的值来更改逻辑处理器的数量。给这个函数传入 1,是通知调度器只能为该程序使用一个逻辑处理器。

Start Goroutines
Waiting To Finish
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m
n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z
Terminating Program

第一个 goroutine 完成所有显示需要花时间太短了,以至于在调度器切换到第二个 goroutine之前,就完成了所有任务。这也是为什么会看到先输出了所有的大写字母,之后才输出小写字母。我们创建的两个 goroutine 一个接一个地并发运行,独立完成显示字母表的任务。

代码清单 6-6 如何修改逻辑处理器的数量

import "runtime"
// 给每个可用的核心分配一个逻辑处理器
runtime.GOMAXPROCS(runtime.NumCPU())

包 runtime 提供了修改 Go 语言运行时配置参数的能力。在代码清单 6-6 里,我们使用两个 runtime 包的函数来修改调度器使用的逻辑处理器的数量。函数 NumCPU 返回可以使用的物理处理器的数量。因此,调用 GOMAXPROCS 函数就为每个可用的物理处理器创建一个逻辑处理器。需要强调的是,使用多个逻辑处理器并不意味着性能更好。在修改任何语言运行时配置参数的时候,都需要配合基准测试来评估程序的运行效果。如果给调度器分配多个逻辑处理器,我们会看到之前的示例程序的输出行为会有些不同。让我们把逻辑处理器的数量改为 2(runtime.GOMAXPROCS(2)),并再次运行第一个打印英文字母表的示例程序

代码清单 6-8 listing07.go 的输出

Create Goroutines
Waiting To Finish
A B C a D E b F c G d H e I f J g K h L i M j N k O l P m Q n R o S p T
q U r V s W t X u Y v Z w A x B y C z D a E b F c G d H e I f J g K h L
i M j N k O l P m Q n R o S p T q U r V s W t X u Y v Z w A x B y C z D
a E b F c G d H e I f J g K h L i M j N k O l P m Q n R o S p T q U r V
s W t X u Y v Z w x y z
Terminating Program

如果仔细查看代码清单 6-8 中的输出,会看到 goroutine 是并行运行的。两个 goroutine 几乎是同时开始运行的,大小写字母是混合在一起显示的。这是在一台 8 核的电脑上运行程序的输出,所以每个 goroutine 独自运行在自己的核上。记住,只有在有多个逻辑处理器且可以同时让每个goroutine 运行在一个可用的物理处理器上的时候, goroutine 才会并行运行。

推荐阅读更多精彩内容

  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 303评论 0 4
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 3,280评论 0 23
  • 必备的理论基础 1.操作系统作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口。 管理调度进程,并将多个进程对硬件...
    drfung阅读 584评论 0 1
  • 在几十年前,我们那些前辈们上大学时,学生刻苦努力学习新知识,求知若渴,虚怀若谷,一颗赤子之心无疑。老师也是...
    深尘阅读 75评论 0 0
  • 通过包饺子,我受到了一些启发,想起我们的教学也是一样,要以学生为本,学生是课堂的主人,要把主动权交给学生,教师要起...
    藤县069黎献清阅读 92评论 3 1