golang ----并发 && 并行
Go 语言的线程是并发机制,不是并行机制。
那么,什么是并发,什么是并行?
并发是不同的代码块交替执行,也就是交替可以做不同的事情。
并行是不同的代码块同时执行,也就是同时可以做不同的事情。
举个生活化场景的例子:
你正在家看书,忽然电话来了,然后你接电话,通话完成后继续看书,这就是并发,看书和接电话交替做。
如果电话来了,你一边看书一遍接电话,这就是并行,看书和接电话一起做。
先看实例
package main import (
"fmt"
"runtime"
"sync"
) func main() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
fmt.Println("go routine 1 i: ", i)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
fmt.Println("go routine 2 i: ", i)
wg.Done()
}(i) }
wg.Wait()
}
输出:
go routine 2 i: 9 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 1 i: 10 go routine 2 i: 0 go routine 2 i: 1 go routine 2 i: 2 go routine 2 i: 3 go routine 2 i: 4 go routine 2 i: 5 go routine 2 i: 6 go routine 2 i: 7 go routine 2 i: 8
为什么会是这样的结果?
并发不等于并行
golang的核心开发人员Rob Pike专门提到了这个话题(有兴趣可以看这个视频或者看原文PPT)
虽然我们在for循环中使用了go 创建了一个goroutine,我们想当然会认为,每次循环变量时,golang一定会执行这个goroutine,然后输出当时的变量。 这时,我们就陷入了思维定势。 默认并发等于并行。
诚然,通过go创建的goroutine是会并发的执行其中的函数代码。 但一定会按照我们所设想的那样每次循环时执行吗? 答案是否定的!
Rob Pike专门提到了golang中并发指的是代码结构中的某些函数逻辑上可以同时运行,但物理上未必会同时运行。而并行则指的就是在物理层面也就是使用了不同CPU在执行不同或者相同的任务。
golang的goroutine调度模型决定了,每个goroutine是运行在虚拟CPU中的(也就是我们通过runtime.GOMAXPROCS(1)所设定的虚拟CPU个数)。 虚拟CPU个数未必会和实际CPU个数相吻合。每个goroutine都会被一个特定的P(虚拟CPU)选定维护,而M(物理计算资源)每次回挑选一个有效P,然后执行P中的goroutine。
每个P会将自己所维护的goroutine放到一个G队列中,其中就包括了goroutine堆栈信息,是否可执行信息等等。默认情况下,P的数量与实际物理CPU的数量相等。因此当我们通过循环来创建goroutine时,每个goroutine会被分配到不同的P队列中。而M的数量又不是唯一的,当M随机挑选P时,也就等同随机挑选了goroutine。
在本题中,我们设定了P=1。所以所有的goroutine会被绑定到同一个P中。 如果我们修改runtime.GOMAXPROCS的值,就会看到另外的顺序。 如果我们输出goroutine id,就可以看到随机挑选的效果:
package main import (
"fmt"
"runtime"
"strconv"
"strings"
"sync"
) func main() {
wg := sync.WaitGroup{}
wg.Add(20)
for i := 0; i < 10; i++ {
go func() {
var buf [64]byte
n := runtime.Stack(buf[:], false) idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0] id, err := strconv.Atoi(idField)
if err != nil {
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
}
fmt.Printf("go routine 1 : i -- %d id-- %d\n ", i, id)
wg.Done()
}()
}
for i := 0; i < 10; i++ {
go func(i int) {
var buf [64]byte
n := runtime.Stack(buf[:], false)
idField := strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
id, err := strconv.Atoi(idField)
if err != nil {
panic(fmt.Sprintf("cannot get goroutine id: %v", err))
}
fmt.Printf("go routine 2 : i -- %d id-- %d \n", i, id)
wg.Done()
}(i) }
wg.Wait()
}
输出:
go routine 1 : i -- 10 id-- 20
go routine 1 : i -- 10 id-- 19
go routine 1 : i -- 10 id-- 22
go routine 1 : i -- 10 id-- 25
go routine 1 : i -- 10 id-- 28
go routine 2 : i -- 8 id-- 37
go routine 1 : i -- 10 id-- 23
go routine 1 : i -- 10 id-- 21
go routine 2 : i -- 0 id-- 29
go routine 2 : i -- 9 id-- 38
go routine 2 : i -- 1 id-- 30
go routine 2 : i -- 5 id-- 34
go routine 2 : i -- 2 id-- 31
go routine 2 : i -- 6 id-- 35
go routine 2 : i -- 3 id-- 32
go routine 1 : i -- 10 id-- 24
go routine 2 : i -- 7 id-- 36
go routine 1 : i -- 10 id-- 27
go routine 2 : i -- 4 id-- 33
go routine 1 : i -- 10 id-- 26
我们再回到这道题中,虽然在循环中通过go定义了一个goroutine。但我们说到了,并发不等于并行。因此虽然定义了,但此刻不见得就会去执行。需要等待M选择P之后,才能去执行goroutine。 关于golang中goroutine是如何进行调度的(GPM模型),可以参考Scalable Go Scheduler Design Doc或者LearnConcurrency
这时应该就可以理解为什么会先输出goroutine2然后再输出goroutine1了吧。
下面我们来解释为什么goroutine1中输出的都是10.
goroutine如何绑定变量
在golang的for循环中,golang每次都使用相同的变量实例(也就是题中所使用的i)。 而golang之间是共享环境变量的。
当调度到这个goroutine时,它就直接读取所保存的变量地址,此时就会出现一个问题:goroutine保存的只是变量地址,所以变量是有可能被修改的。
再结合题中的for循环,每次使用的都是同一个变量地址,也就是说i每次都在变化,到循环结束之时,i就变成了10. 而goroutine中保存的也只有i的内存地址而已,所以当goroutine1执行时,毫不犹豫的就把i的内容读了出来,多少呢? 10!
但为什么goroutine2不是10呢?
反过来看goroutine2,就容易理解了。因为在每次循环中都重新生成了一个新变量,然后每个goroutine保存的是各自新变量的地址。 这些变量相互之间互不干扰,不会被任何人所篡改。因此在输出时,会从0 - 9依次输出。
golang ----并发 && 并行的更多相关文章
- 马蜂窝搜索基于 Golang 并发代理的一次架构升级
		
搜索业务是马蜂窝流量分发的重要入口.很多用户在使用马蜂窝时,都会有目的性地主动搜索与自己旅行需求相关的各种信息,衣食住行,事无巨细,从而做出最符合需求的旅行决策. 因此在马蜂窝,搜索业务交互的下游模块 ...
 - 消息/事件, 同步/异步/协程, 并发/并行  协程与状态机  ——从python asyncio引发的集中学习
		
我比较笨,只看用await asyncio.sleep(x)实现的例子,看再多,也还是不会. 已经在unity3d里用过coroutine了,也知道是“你执行一下,主动让出权限:我执行一下,主动让出权 ...
 - Golang并发原理及GPM调度策略(一)
		
其实从一开始了解到go的goroutine概念就应该想到,其实go应该就是在内核级线程的基础上做了一层逻辑上的虚拟线程(用户级线程)+ 线程调度系统,如此分析以后,goroutine也就不再那么神秘了 ...
 - Golang - 并发编程
		
目录 Golang - 并发编程 1. 并行和并发 2. go语言并发优势 3. goroutine是什么 4. 创建goroutine 5. runtime包 6. channel是什么 7. ch ...
 - golang并发编程
		
golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止go ...
 - 多线程爬坑之路--并发,并行,synchonrized同步的用法
		
一.多线程的并发与并行: 并发:多个线程同时都处在运行中的状态.线程之间相互干扰,存在竞争,(CPU,缓冲区),每个线程轮流使用CPU,当一个线程占有CPU时,其他线程处于挂起状态,各线程断续推进. ...
 - Golang 并发简介
		
并发概要 随着多核CPU的普及, 为了更快的处理任务, 出现了各种并发编程的模型, 主要有以下几种: 模型名称 优点 缺点 多进程 简单, 隔离性好, 进程间几乎无影响 开销最大 多线程 目前使用最多 ...
 - golang并发(1)介绍
		
概述 简而言之,所谓并发编程是指在一台处理器上“同时”处理多个任务. 随着硬件的发展,并发程序变得越来越重要.Web服务器会一次处理成千上万的请求.平板电脑和手机app在渲染用户画面同时还会后台执行各 ...
 - golang 并发顺序输出数字
		
参考 package main import ( "fmt" "sync/atomic" "time" ) func main() { va ...
 
随机推荐
- python基础-面向对象编程之组合
			
面向对象编程之组合 定义:一个对象中拥有另一个或其他多个对象的属性和方法. 作用:减少代码的冗余,降低耦合度 关于耦合度的说明 耦合:通俗地讲,就是相互作用,相互影响的意思 耦合度越高,程序的可扩展性 ...
 - SpringCloud学习第三章-springcloud 父项目创建
			
父项目 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns=" ...
 - Linux计划任务管理
			
计划任务 类型: 一次性计划任务 周期性计划任务 一次性计划任务 前提: atd服务必须运行 [root@wei init.d]# yum -y install at ...
 - JavaScript中eval函数的用法
			
1. eval函数会计算传给的字符串, 并把作为脚本代码来执行. eval(str) 此函数接受一个字符串作为参数,并把str当做一段JavaScript脚本代码来执行,如果str执行结果返回一个值则 ...
 - PHP技术栈
			
本文旨在给要学习 PHP 的新手一个大概的认知轮廓,在心里有个学习的结构,有的放矢,避免走太多弯路.大神请忽略. 入门阶段 预备知识 1.掌握基本HTML.JS.CSS语法:熟悉 Bootstrap. ...
 - 洛谷P2463 [SDOI2008]Sandy的卡片(后缀数组SA + 差分 + 二分答案)
			
题目链接:https://www.luogu.org/problem/P2463 [题意] 求出N个串中都出现的相同子串的最长长度,相同子串的定义如题:所有元素加上一个数变成另一个,则这两个串相同,可 ...
 - 利用多线程使socket服务端可以与多个客户端同时通讯
			
利用多线程使socket服务端可以与多个客户端同时通讯 server import socket 1. 符合TCP协议的手机 server = socket.socket(socket.AF_INET ...
 - python3字符串的方法及注释/ 字符串格式化符号含义及格式化符号含义
			
capitalize() 把字符串的第一个字符改为大写 casefold() 把整个字符串的所有字符改为小写 center(width) 将字符串居中,并使用空格填充至长度wid ...
 - turtle模块
			
turtle(海龟)绘图用法 import turtle -->调出turtle库 setup()-->设置窗体大小和位置 turtle.setup(width,height,startx ...
 - Kubernetes部署Spring Boot应用
			
SpringBoot项目 新建springboot项目 @RestController public class HelloWorldController { @RequestMapping(&quo ...