【Go语言】【16】GO语言的并发

By | 07月26日
Advertisement

在写该文之前一直犹豫,是把Go的并发写的面面俱到显得高大尚一些,还是简洁易懂一些?今天看到一个新员工在学习Java,突然间想起第一次接触Java的并发时,被作者搞了一个云里雾里,直到现在还有阴影,所以决定本文从简。哈哈,说笑了,言归正传。

Go的并发真的很简单,所以本文不罗嗦进程、线程、协程、信号量、锁、调度、时间片等乱七八糟的东西,因为这些不影响您理解Go的并发。先看一个小例子:

package main

import "fmt"

func Add(i, j int) {

sum := i + j

fmt.Println(i, " + ", j, " = ", sum)

}

func main() {

for i := 0; i < 10; i++ {

Add(i, i)

}

}

这个例子很简单吧,说白了就是计算0+0、1+1、2+2、3+3、.......、9+9之和,并打印出来,运行结果显而易见:

【Go语言】【16】GO语言的并发

这里没有使用并发呀,好吧,为了提升计算效率,在main的for循环中使用并发,把代码修改如下:

package main

import "fmt"

func Add(i, j int) {

sum := i + j

fmt.Println(i, " + ", j, " = ", sum)

}

func main() {

for i := 0; i < 10; i++ {

go Add(i, i)

}

}

没有花眼吧,前面加了一个go,这就并发了?

嗯,这就并发了。

在方法Add()之前增加了一个关键字go,相当于告诉Go编译器启动一个goroutine,然后把Add()方法放到goroutine中执行。

什么是goroutine?

有人把它翻译为协程,说实话挺反感的,有些单词还是不要翻译为好,比如Context,经常写Web程序的人会遇到,有人把它翻译为上下文;再如payload,经常做渗透的人会使用,怎么翻译好呢?还是不翻译了吧。

该怎么理解goroutine?

【Go语言】【16】GO语言的并发

如上图所示,main()方法所在的goroutine上又创建了10个goroutine,每个goroutine各自跑一个Add()方法

OK,运行一下该程序,结果如下:

【Go语言】【16】GO语言的并发

咦,怎么都没有,说好的运行结果呢?

这是因为main()所在的goroutine创建10个goroutine后,它里面的逻辑已执行完,那么main()就退出了,它根本就不管这10个goroutine的死活。从结果也能看出,当main()退出时,这10个goroutine没有一个执行完,所以结果什么都没有打印。

如何解决这个问题呢?

一种比较容易想到的但又比较垃圾的解决办法是:“让main()等一会儿”,下面我们修改一个这个程序


package main

import (

"fmt"

"time"

)

func Add(i, j int) {

sum := i + j

fmt.Println(i, " + ", j, " = ", sum)

}

func main() {

for i := 0; i < 10; i++ {

go Add(i, i)

}

time.Sleep(time.Second * 3) // main()所在goroutine休息3秒钟

}

首先引入"time"这个包,然后调用time.Sleep()方法,让main()所在goroutine等3s,等其它10个goroutine都运行完,这种解决办法就是“马儿你慢些跑呀慢些跑” :)

运行一下结果:

【Go语言】【16】GO语言的并发

可能您会问,为何要等3秒钟而不是2秒钟?

我只能学着印度老外,一边摇头一边微笑地告诉您,我是蒙的,因为我也不知道确切地等多长时间,所以是一种垃圾的解决办法。

那有没有一种通知机制呢?当一个goroutine执行完毕后,就告诉主goroutine(即main()方法所在的goroutine):“嘿,哥们,我执行完了,你想干嘛就干嘛吧!”

有,这就是Go语言的亮点,十分耀眼的一个亮点:channel

什么是channel?

说白了就是一个通道(建议还是不翻译的为好),一个goroutine执行完毕后,就告诉主goroutine,I'm over!怎么告诉呢?就是向channel中写一个数据。

怎么向channel中写一个数据呢?

OK,follow me,要想写一个数据到channel则必须有一个channel不是?所以:

(1)建立一个channel



【备注】:所谓channel也是一种Go的类型,与int、float64、string、bool、struct、slice、map等同等地位



var ch chan int // 声明一个变量为ch,它的类型为chan类型,这个channel里面可以存放int型的值

ch = make(chan int) // 使用make关键字初始一个长度为0的通道

当然声明和初始化可以一块来

var ch chan int = make(chan int)

(2)向channel中写一个数据

ch <- 1

就这么简单,使用符号”<-“,前面声明了channel的类型为int ,所以就把1写入ch;若声明channel类型为bool,就可以把布尔值写入channel,即ch <- true

(3)从channel中读数据

<- ch

嗯,还是这么简单

无论写还是读都是用符号”<-“,就看<-后面是谁

OK,既然知道有channel这东东了,我们修改一下上面的程序:


package main

import (

"fmt"

// "time" // 删除掉time包

)

var ch chan int = make(chan int) // 初始化一个类型为channel的变量ch,其中channel里面放int型数据

func Add(i, j int) {

sum := i + j

fmt.Println(i, " + ", j, " = ", sum)

ch <- i // 这个方法执行完,意味着方法所属的goroutine即将退出,就告诉主goroutine,I'm完事了

}

func main() {

for i := 0; i < 10; i++ {

go Add(i, i)

}

/*

* 由于有10个goroutine,所以从channel中读10次

* 这10个goroutine都告诉主gorouinte说完事了,那么主gorouinte也就退出了

*/

for i := 0; i < 10; i++ {

fmt.Println("i=", <-ch)

}

// time.Sleep(time.Second * 3) // 不用这种机制了,留着也没有用

}

运行结果如下:

【Go语言】【16】GO语言的并发

目的达到了,我好人做到底,再解释一下:

【Go语言】【16】GO语言的并发

可以这样理解,有10个厨师1个端菜工,这10个厨师各自做各自的菜,做完之后就放到channel,这个端菜工就从channel中取菜。当channel中没有菜时,端菜工就一直等待直到有菜为止;厨师做好一个菜后,发现channel中没有菜,就把自己的菜放到channel中,若发现channel在有菜还没有端正,厨师就拿着自己的菜一直等到channel中的菜被端菜工端走后再把自己的菜放进去。

可能您又要说了,这种channel很类似同步操作,这个channel只能放一个菜,端菜工端一个菜;channel中没有菜端菜工等待;channel中有菜厨师等待。效率不高呀。

好吧,Go设计师已提前为您想好处理办法了,即这个channel可以放10个菜,只要这个channel还没有放满10个菜,厨师就可以向上面放,这样厨师就不用等待了。剩下的就是端菜工要提高自己的工作效率了。

怎么放10个菜?

var ch chan int = make(chan int,10)

OK,搞定!代码如下:


package main

import (

"fmt"

// "time"

)

var ch chan int = make(chan int, 10)

func Add(i, j int) {

sum := i + j

fmt.Println(i, " + ", j, " = ", sum)

ch <- i

}

func main() {

for i := 0; i < 10; i++ {

go Add(i, i)

}

for i := 0; i < 10; i++ {

fmt.Println("i=", <-ch)

}

// time.Sleep(time.Second * 3)

}

运行结果如下:

【Go语言】【16】GO语言的并发

仔细看,再仔细看,看出什么东西来了没有?若没有,请与上一个运行结果对比着看 :)

还是没有看来?

没有发现这两个基本上是一模一样的吗?除了运行程序所花的时间不同之外!

从运行时间上来看,好像效率提升了一些,这是因为厨师不用等待了,一旦指明了channel的容量,相对于厨师来说就变成异步的了;没有指明channel容量,相对于厨师来说就是同步的。

同步异步不是我想让您观察的重点,您难道没有发现0+0、1+1、2+2、3+3、......、9+9,这个顺序太正常了吗?若真正并发的话,这个顺序肯定是乱的!

看一下我电脑信息:

【Go语言】【16】GO语言的并发

好呆CPU也是四核的,并发的顺序是这么的正常,太不可思议了 :)

好吧,我再解开这谜团吧

我用的Go版本是1.4,可以在命令窗口中执行go version查看。由于Go语言1.4版本对多核的处理还没有做太多的改进,据说1.5版本有突破,后面可以关注一下,所以在这个版本还是使用的一个核。对于单核CPU进程、线程在执行的过程中,系统会把运行的CPU强行切给另一个进程或线程。

有人如果看过其他人的博客、书,都会把goroutine翻译为协程,所谓协程就是用户态的线程,可以这样理解:”一个协程在执行时,系统不会强行切换时间片“。即一个goroutine在执行的过程中,Go语言会让这个goroutine疯狂地执行,直到它运行完为止,再让另外一个gorouinte运行,所以从结果来看运行顺序是固定的。

如果利用多核?

Go语言也提供了一种方式,具体代码如下:


package main

import (

"fmt"

// "time"

"runtime" // 引入runtime包

)

var ch chan int = make(chan int, 10)

func Add(i, j int) {

sum := i + j

fmt.Println(i, " + ", j, " = ", sum)

ch <- i

}

func main() {

runtime.GOMAXPROCS(runtime.NumCPU()) // 让Go使用多个核

for i := 0; i < 10; i++ {

go Add(i, i)

}

for i := 0; i < 10; i++ {

fmt.Println("i=", <-ch)

}

// time.Sleep(time.Second * 3)

}

多运行几次,结果如下:

【Go语言】【16】GO语言的并发

上面就是Go的并发核心内容,当然还有关于并发的其它内容,如单向写channel、单向读channel、传统并发方式等内容。本文就先写到这里,内容多了不容易消化 :)

本文出自 “青客” 博客,请务必保留此出处

Similar Posts:

  • 初学Go语言,对channel、并发的疑问

    初学go语言,对channel.并发感觉不太好理解.希望高人指点下. 下面的代码是我看到一本书上的,就是讲解channel的. package main import ( "fmt" "math/rand" ) func test(ch chan int, i int) { ch <- rand.Int() fmt.Println(i, "go...") } func main() { chs := make([]chan int, 10)

  • Scala语言中基于Actor的并发编程的机制初步

    DT大数据梦工厂-Scala深入浅出实战中级--进阶经典:第66讲:Scala并发编程实战初体验及其在Spark源码中的应用解析 本期视频通过代码实战详解了Java语言基于加锁的并发编程模型的弊端以及Scala语言中基于Actor的并发编程的机制,并展示了在Spark中基于Scala语言的Actor而产生的消息驱动框架Akka的使用. Object Hello_Actor{ def main(args:Array[String]){ First_Actor.start Second_Actor.

  • iPhone开发入门(7)— 从C/C++语言到Objective-C语言

    博主:易飞扬 原文链接 : http://www.yifeiyang.net/iphone-development-introduction-7-from-the-c-c-language-to-objective-c-language/ 转载请保留上面文字. 语法 概念 SEL,IMP的定义 方法的定义 Class的定义 发送消息与函数调用的不同 Target-Action Paradigm iPhone开发入门(7)--- 从C/C++语言到Objective-C语言 Objective-C

  • 二十一天学通C语言:C语言中指针排序

    二十一天学通C语言:C语言中指针排序 本文节选自<21天学通C语言>一书 使用指针从标准输入获取三个整数,并求其中的最大值. [提示]使用三个指针变量分别指向三个int型变量,使用scanf函数分别为其赋值:然后比较各个指针指向的值,将第一个指针赋值为最大值的地址.实现方法如示例代码14-8所示. 示例代码14-8 01 #include <stdio.h> 02 03 int main(void) { 04 int a, b, c; 05 /* 定义并初始化三个指针 */ 06

  • 动态语言和静态语言的比较

    动态语言和静态语言的比较 一 .静态语言的优势到底在哪? 来自robbin 摘自 http://www.javaeye.com/article/33971?page=7 引用 是像Java或者C#这样强类型的准静态语言在实现复杂的业务逻辑.开发大型商业系统.以及那些生命周期很长的应用中也有着非常强的优势 这是一个存在于大家心里常识了.我承认我自己在潜意识里面也觉得静态强类型语言适合开发复杂,大型系统.而弱类型脚本语言不适合开发太复杂,太大型的项目.但是在参与这个讨论过程中,我突然开始置疑这个观点

  • ※C++随笔※=&gt;☆C++基础☆=&gt;※№ C语言与C++语言之间关系

    很多时候我们对于C和C++的区别不是很清楚,以至于弄混的情况并不少见.那C语言和C++语言到底是怎么回事呢? 首先,我们来看下百度百科对语言和C++语言描述,相对而说也还算是比较权威的. C语言 C语言是一种计算机程序设计语言,它既具有高级语言的特点,又具有汇编语言的特点.它由美国贝尔研究所的D.M.Ritchie于1972年推出,1978年后,C语言已先后被移植到大.中.小及微型机上,它可以作为工作系统设计语言,编写系统应用程序,也可以作为应用程序设计语言,编写不依赖计算机硬件的应用程序.它的

  • 编译性语言、解释性语言和脚本语言收藏

    什么是编译性语言.解释性语言和脚本语言 计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能值型高级语言编写的程序. 翻译的方式有两种,一个是编译,一个是解释.两种方式只是翻译的时间不同.编译型语言写的程序执行之前,需要一个专门的编译过程,把程序编译成为机器语言 的文件,比如exe文件,以后要运行的话就不用重新翻译了,直接使用编译的结果就行了(exe文件),因为翻译只做了一次,运行时不需要翻译,所以编译型 语言的程序执行效率高. 解释则不同,解释性语言

  • 浅析服务器语言跟客户端语言的区别

    asp.asp.net.jsp和php与javascript之间的关系可以一起回答: 前面四种是服务端语言,而javascript是客户端语言.服务端语言和客户端语言有什么区别呢? 服务端语言主要是用来生成html+javascript这样的客户端页面的,它们不会被实际发送到客户端,而是先在服务器端的计算机上执行,然后生成客户端代码,再将这些代码发送给浏览网页的客户端. asp是个特例,它本身使用vbscript或javascript语法,它本身只提供了五大对象,这五个对象主要是面向服务端应用的

  • D语言入门---D语言库tango

    D语言入门---D语言库tango 首发 D语言论坛: http://bbs.d-programming-language-china.org/ 本文以Tango 0.99.5 , DMD 1.027为例. 下载D语言库tango 下载网址: svn下载网址: http://svn.dsource.org/projects/tango/trunk/ 用svn客户端工具导出到 dmd/tango 目录. dmd/tango 目录在哪里?确认下面文件存在: dmd/bin/dmd.exe dmd/t

  • 什么是编译性语言、解释性语言和脚本语言及java

    摘自:http://www.diybl.com/course/3_program/java/javajs/20081124/152528.html 什么是编译性语言.解释性语言和脚本语言 计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能值型高级语言编写的程序. 翻译的方式有两种,一个是编译,一个是解释.两种方式只是翻译的时间不同.编译型语言写的程序执行之前,需要一个专门的编译过程,把程序编译成为机器语言的文件,比如exe文件,以后要运行的话就不用重

  • 【c/c++笔记】高字节和低字节 动态语言和静态语言等

    一)字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了).其实大部分人在实际的开发中都很少会直接和字节序打交道.唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题. 在所有的介绍字节序的文章中都会提到字节序分为两类:Big-Endian和Little-Endian,引用标准的Big-Endian和Little-Endian的定义如下: a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存

Tags: