Go语言的并发和并行

不知道你有没有注意到一个现象,还是这段代码,如果我跑在两个goroutines里面的话:

var quit chan int = make(chan int)

func loop() {
for i := ; i < ; i++ {
fmt.Printf("%d ", i)
}
quit <-
} func main() {
// 开两个goroutine跑函数loop, loop函数负责打印10个数
go loop()
go loop() for i := ; i < ; i++ {
<- quit
}
}

我们观察下输出:

                   

这是不是有什么问题??

以前我们用线程去做类似任务的时候,系统的线程会抢占式地输出, 表现出来的是乱序地输出。而goroutine为什么是这样输出的呢?

goroutine是在并行吗?

我们找个例子测试下:

package main

import "fmt"
import "time" var quit chan int func foo(id int) {
fmt.Println(id)
time.Sleep(time.Second) // 停顿一秒
quit <- // 发消息:我执行完啦!
} func main() {
count :=
quit = make(chan int, count) // 缓冲1000个数据 for i := ; i < count; i++ { //开1000个goroutine
go foo(i)
} for i := ; i < count; i++ { // 等待所有完成消息发送完毕。
<- quit
}
}

让我们跑一下这个程序(之所以先编译再运行,是为了让程序跑的尽量快,测试结果更好):

go build test.go
time ./test
./test .01s user .01s system % cpu 1.016 total

我们看到,总计用时接近一秒。 貌似并行了!

我们需要首先考虑下什么是并发, 什么是并行

并行和并发

从概念上讲,并发和并行是不同的, 简单来说看这个图片

  • 两个队列,一个Coffee机器,那是并发
  • 两个队列,两个Coffee机器,那是并行

那么回到一开始的疑问上,从上面的两个例子执行后的表现来看,多个goroutine跑loop函数会挨个goroutine去进行,而sleep则是一起执行的。

这是为什么?

默认地, Go所有的goroutines只能在一个线程里跑 。

也就是说, 以上两个代码都不是并行的,但是都是是并发的。

如果当前goroutine不发生阻塞,它是不会让出CPU给其他goroutine的, 所以例子一中的输出会是一个一个goroutine进行的,而sleep函数则阻塞掉了 当前goroutine, 当前goroutine主动让其他goroutine执行, 所以形成了逻辑上的并行, 也就是并发。

真正的并行

为了达到真正的并行,我们需要告诉Go我们允许同时最多使用多个核。

回到起初的例子,我们设置最大开2个原生线程, 我们需要用到runtime包(runtime包是goroutine的调度器):

import (
"fmt"
"runtime"
) var quit chan int = make(chan int) func loop() {
for i := ; i < ; i++ { //为了观察,跑多些
fmt.Printf("%d ", i)
}
quit <-
} func main() {
runtime.GOMAXPROCS() // 最多使用2个核 go loop()
go loop() for i := ; i < ; i++ {
<- quit
}
}

这下会看到两个goroutine会抢占式地输出数据了。

我们还可以这样显式地让出CPU时间:

func loop() {
for i := ; i < ; i++ {
runtime.Gosched() // 显式地让出CPU时间给其他goroutine
fmt.Printf("%d ", i)
}
quit <-
} func main() { go loop()
go loop() for i := ; i < ; i++ {
<- quit
}
}

观察下结果会看到这样有规律的输出:

                   

其实,这种主动让出CPU时间的方式仍然是在单核里跑。但手工地切换goroutine导致了看上去的“并行”。

runtime调度器

runtime调度器是个很神奇的东西,但是我真是但愿它不存在,我希望显式调度能更为自然些,多核处理默认开启。

关于runtime包几个函数:

  • Gosched 让出cpu

  • NumCPU 返回当前系统的CPU核数量

  • GOMAXPROCS 设置最大的可同时使用的CPU核数

  • Goexit 退出当前goroutine(但是defer语句会照常执行)

总结

我们从例子中可以看到,默认的, 所有goroutine会在一个原生线程里跑,也就是只使用了一个CPU核。

在同一个原生线程里,如果当前goroutine不发生阻塞,它是不会让出CPU时间给其他同线程的goroutines的,这是Go运行时对goroutine的调度,我们也可以使用runtime包来手工调度。

本文开头的两个例子都是限制在单核CPU里执行的,所有的goroutines跑在一个线程里面,分析如下:

  • 对于代码例子一(loop函数的那个),每个goroutine没有发生堵塞(直到quit流入数据), 所以在quit之前每个goroutine不会主动让出CPU,也就发生了串行打印
  • 对于代码例子二(time的那个),每个goroutine在sleep被调用的时候会阻塞,让出CPU, 所以例子二并发执行。

Golang 并发Groutine实例解读(一)的更多相关文章

  1. Golang 并发Groutine实例解读(二)

    go提供了sync包和channel机制来解决协程间的同步与通信. 一.sync.WaitGroup sync包中的WaitGroup实现了一个类似任务队列的结构,你可以向队列中加入任务,任务完成后就 ...

  2. Golang 并发Groutine详解

    概述 1.并行和并发 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行. 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在 ...

  3. 【WCF--初入江湖】08 并发与实例模式

    08 并发与实例模式 1. 实例上下文模式   一个服务代理:servicePoxy ChannelFactory<IService1> factoryservicel = new Cha ...

  4. golang并发编程

    golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止go ...

  5. 马蜂窝搜索基于 Golang 并发代理的一次架构升级

    搜索业务是马蜂窝流量分发的重要入口.很多用户在使用马蜂窝时,都会有目的性地主动搜索与自己旅行需求相关的各种信息,衣食住行,事无巨细,从而做出最符合需求的旅行决策. 因此在马蜂窝,搜索业务交互的下游模块 ...

  6. Golang 并发简介

    并发概要 随着多核CPU的普及, 为了更快的处理任务, 出现了各种并发编程的模型, 主要有以下几种: 模型名称 优点 缺点 多进程 简单, 隔离性好, 进程间几乎无影响 开销最大 多线程 目前使用最多 ...

  7. golang 并发顺序输出数字

    参考 package main import ( "fmt" "sync/atomic" "time" ) func main() { va ...

  8. Python并发编程实例教程

    有关Python中的并发编程实例,主要是对Threading模块的应用,文中自定义了一个Threading类库. 一.简介 我们将一个正在运行的程序称为进程.每个进程都有它自己的系统状态,包含内存状态 ...

  9. Golang并发原理及GPM调度策略(一)

    其实从一开始了解到go的goroutine概念就应该想到,其实go应该就是在内核级线程的基础上做了一层逻辑上的虚拟线程(用户级线程)+ 线程调度系统,如此分析以后,goroutine也就不再那么神秘了 ...

随机推荐

  1. zookeeper集群崩溃处理

    今天在私有化项目中遇到如下问题: 1.客户反馈用户登录返回303 2.登录服务器查看是大量的log将服务器磁盘空间占用殆尽,导致所有服务进程仍旧存在但是监听端口失败,服务不可用 3.清理日志文件 4. ...

  2. (原创)PetaPoco使用小记(2014-5-5更新)

    接触PetaPoco已经有一段时间了,为了全面了解一下PetaPoco,刚好结合目前在做的一个项目,对常用的几个业务操作用PetaPoco进行改写,如增删改查.分页以及存储过程的调用,在文章的最后附上 ...

  3. 通过css使用background-color为背景图添加遮罩效果

    一个div同时设置background-color和background-image的话,color是处于img层下方的,无法实现遮罩效果,所以需要再创建一个div作为其子div,然后设置子div的背 ...

  4. 如何把OpenWrt安装到PC?

    前言 什么是openwrt? 它是一个适用于路由器的Linux发行版.和其他Linux发行版一样,它也内置了包管理工具,你可以从一个软件仓库里直接安装软件.OpenWrt可以用在所有需要嵌入式Linu ...

  5. TOJ_12470

    #include <stdio.h> struct node{ int x; int y; int step;}first; int zx[4]={-1,0,1,0};int zy[4]= ...

  6. django 获取request请求对象及response响应对象中的各种属性值

    django request对象和HttpResponse对象 HttpRequest对象(除非特殊说明,所有属性都是只读,session属性是个例外) HttpRequest.scheme 请求方案 ...

  7. 算法(Algorithm)

    算法就是"把解决问题的步骤无一遗漏地用文字或图表示出来". 在解决问题的步骤中,有了与直觉相关的因素,就不是算法了.既然不是算法,也就不能用程序表示了.

  8. Java并发编程实践读书笔记(3)任务执行

    类似于Web服务器这种多任务情况时,不可能只用一个线程来对外提供服务.这样效率和吞吐量都太低. 但是也不能来一个请求就创建一个线程,因为创建线程的成本很高,系统能创建的线程数量是有限的. 于是Exec ...

  9. P1631 序列合并

    P1631 序列合并 有两个长度都是N的序列A和B,在A和B中各取一个数相加可以得到N^2N2个和,求这N^2N2个和中最小的N个. 对于100%的数据中,满足1<=N<=100000. ...

  10. OS之进程管理 --- 死锁

    什么是死锁 在正常操作模式下,进程按如下顺序来使用资源: 申请:进程请求资源 使用:进程对资源进行操作 释放:进程释放资源 当一组进程中的每一个进程度在等待一个事件,而这事件只能有一组进程的另一个进程 ...