一、创建goroutine

1)在go语言中,每一个并发的执行单元叫做一个goroutine;

2)当一个程序启动时,其主函数即在一个单独的goroutine中运行,一般这个goroutine是主goroutine;如果想要创建新的goroutine,只需要再执行普通函数或者方法的的前面加上关键字go。

3)go是如何实现一个多线程程序?

go语言中很简单,加一个go关键字即可,就变成了一个多线程程序(一个进程跑多个线程)

下面通过一个完整实例来了解一下goroutine:

实例1-1         实例1

1)未加go关键字;

package main

import (
"fmt"
) func hello() {
fmt.Printf("hello\n")
} func main() {
hello()
fmt.Printf("main exited")
}

执行结果如下图所示:

解释:

可以发现hello函数和main函数是同一个线程串行执行,先输出hello,在输出main exited

2) 加上go关键字;

package main

import (
"fmt"
) func hello() {
fmt.Printf("hello\n")
} func main() {
go hello()
fmt.Printf("main exited")
}

执行结果如下:

解释:

加上go关键字后,可以发现hello函数还未执行,程序就退出了。

hello函数加上go关键字后,可以发现其不再是和main函数同步的执行(串行)了,应该是并发执行了,hello函数相当于是新起了一个线程,和main函数这个线程是独立的,一共是两个线程在执行;

在go语言中,以main函数所运行的主线程为准,如果main函数线程(主线程)执行完毕,还有其他线程(附属主线程的子线程)在执行,这些未执行完毕的线程也会被强制关闭。(main函数所在的主线程一旦退出,就相当于整个进程退出,进程一退出,这个进程中的其他线程也会被强制关闭退出)

针对我们这个实例:其实就是main函数所在的主线程已经执行完毕了,hello函数所在的线程效率没有main函数所在的主线程高,所以输出结果没有hello函数的内容。

3) 加上go关键字完整版代码;

package main

import (
"fmt"
"time"
) func hello() {
fmt.Printf("hello\n")
} func main() {
go hello()
time.Sleep( * time.Second) //通过睡1秒,保证hello函数所在线程执行
fmt.Printf("main exited")
}

执行结果如下图所示:

解释:

通过sleep 1秒,hello函数所在线程得以执行完毕。

实例1-2         实例2

1)不加go关键字

package main

import (
"fmt"
"time"
) func hello() {
for i := ; i < ; i++ {
fmt.Printf("hello:%d\n", i)
time.Sleep(time.Millisecond * )
}
} func main() {
hello() for i := ; i < ; i++ {
fmt.Printf("main:%d\n", i)
time.Sleep(time.Millisecond * )
}
time.Sleep(time.Second)
}

执行结果如下图所示:

解释:

可以看到不加go关键字,是串行执行。

2)加上go关键字

package main

import (
"fmt"
"time"
) func hello() {
for i := ; i < ; i++ {
fmt.Printf("hello:%d\n", i)
time.Sleep(time.Millisecond * )
}
} func main() {
go hello() for i := ; i < ; i++ {
fmt.Printf("main:%d\n", i)
time.Sleep(time.Millisecond * )
}
time.Sleep(time.Second)
}

执行结果如下图所示:

解释:

通过打印结果可以看到hello函数所在的线程和main函数所在的主线程是并行执行的。

二、创建多个goroutine

实例如下:

package main

import (
"fmt"
"time"
) func numbers() {
for i := ; i <= ; i++ {
time.Sleep( * time.Millisecond) //每隔250ms打印一个整数
fmt.Printf("%d ", i)
}
}
func alphabets() {
for i := 'a'; i <= 'e'; i++ { //字符的底层也是一个整数
time.Sleep( * time.Millisecond) //每隔400ms打印一个字母
fmt.Printf("%c ", i)
}
}
func main() {
go numbers()
go alphabets()
time.Sleep( * time.Millisecond) //主线程等待3秒在退出
fmt.Println("main terminated")
}

执行结果如下:

解释:

根据输出结果,可以看到numbers函数、alphabets函数所在的线程是并行执行的。

(补充:numbers函数、alphabets函数都是新起一个线程,至于哪个线程先启动,这是不一定的,不是说number函数在前,其的就先启动,具体看cpu的调度策略的)

三、进程和线程

1)进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的

一个独立单位。

2)线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更

小的能独立运行的基本单位。

3)一个进程可以创建和撤销多个线程;同一个进程中的多个线程之间可以并发

执行

图示:

四、并发和并行

1)多线程程序在一个核的cpu上运行,就是并发

2)多线程程序在多个核的cpu上运行,就是并行

图示:

解释:

左图(单核)为并发,右图(多核)为并行。

x轴为时间轴,左图并发,同一时间段只能有一个程序在执行,右图并行,同一时间段内可以有多个程序执行。

五、协程和线程

协程

独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于

用户级线程,这些用户级线程的调度也是自己实现的

线程

线程是操作系统起的,由操作系统进行管理;

一个线程上可以跑多个协程,协程是轻量级的线程。

葵花宝典:

操作系统起的线程有一个问题:

操作系统就是内核,操作系统起的线程执行在内核态,用户的程序应用运行在用户态,这是两个不同的形态,如果我们是创建线程的话,线程是操作系统在管理,如果我们在应用程序中创建线程,线程之间要进行切换的话,就需要进入内核态去进行切换,因为这个线程是内核去控制管理的,如果在一个应用程序中创建了几千个线程,这样线程之间的切换每次都要从用户态到内核态去切换,上下文切换比较频繁,用户态到内核态切换是需要消耗资源的,最终的结果就是,上千个线程,上下文切换会造成系统变得非常缓慢,这也是线程的一个缺点,这也是其他语言的缺点,跑几千个线程就跑不动了,所以其他语言是需要搞一个线程池。

但是在go语言的话,go语言已经帮助我们解决了上述问题,协程是用户态的一个概念,协程和操作系统的线程是两回事,一个操作系统的线程可能执行多个协程,协程是go语言帮我们抽象出来的一个东西,可以发现,协程之间的切换不用和内核态去做一个相互切换,仅仅只在用户态进行切换(线程是需要内核来进行控制切换)。用户态切换要比到内核态进行切换性能要高非常多,所以,go语言中,起上千个协程是完全没有问题的,上万个都没问题(起上万个协程的话,对应的可能就是操作系统的几十个线程)。所以说在go语言中,完全不用担心起上千个线程会影响系统,特别是在高并发领域http服务,基本上都是来一个请求,就会起一个协程的。这样你同时有几万个并发请求过来,就有几万个goroutine再跑,这个性能是非常高的。(nginx并不是每一个请求起一个线程,而是通过异步进行处理,一个线程异步的处理多个请求,这对go来说,写一个高并发程序太简单了。http、tcp服务,来一个请求起一个goroutine即可。)

六、goroutine调度模型

图示1:

解释:

M是操作系统的线程,P是一个上下文,G是goroutine

图示2:

解释:

1)goroutine必须要有上下文才会执行,左边蓝色的可以看见已经有一个goroutine在执行了,右边灰色的是等待被执行的goroutine,一个物理线程可以执行多个goroutine,一个goroutine执行完,下一个等待的goroutine被执行,这其实就是go语言中的一个线程调度情况。

2)比如说一个正在执行的goroutine,其需要暂停,这个时候就要做一个上下文切换(语句执行到哪里等的上下文要保存起来)

3)目前上图情况是:一个程序跑了两个物理线程,一个物理线程跑多个goroutine

图示3:

解释:

M0(物理线程)跑了一个G0(goroutine),同时还有3个goroutine在等待执行,当G0有IO操作或者有网络操作时,此时要进行一个写文件操作,写文件是一个慢操作,磁盘和内存写数据速度相差很多倍(磁盘写文件5ms,内存读数据不到1微妙),此时我们花5ms去同步等待时,cpu的资源就浪费了,这种情况的,发现G0是做IO操作的话,就会把G0和M0给剥离出来,然后会新建一个线程M1,接着执行后面的goroutine。

注意:

go语言:一个cpu同一时间一个线程只能跑一个goroutine,其牛逼的地方在于减少了用户态和内核态的切换。

七、设置golang运行的cpu核数

1、主要借助的是runtime包的NumCPU函数;

2、可以通过runtime包中的GOMAXPROCS函数来控制使用cpu的个数;

3、新版本默认跑满所有cpu个数

注意:

在golang1.6版本之前,goroutine程序默认只能跑在1个cpu上,如果要想多核跑的话,就需要利用GOMAXPROCS函数来控制CPU核数,新版本目前是不用设置了,默认是将计算机所有cpu核数跑满。

实例:

我们下面通过一个实例来对比一下:

1)限制使用的cpu核数为1

package main

import (
"fmt"
"runtime"
"time"
) func main() {
cpu := runtime.NumCPU()
runtime.GOMAXPROCS() //只限制使用cpu的1个核 for i := ; i < ; i++ { //起8个goroutine,每个goroutine来跑一个无限循环的匿名函数
go func() {
for { }
}()
}
fmt.Printf("%d\n", cpu)
time.Sleep( * time.Second)
}

执行结果:

CPU示意图:

我们可以发现cpu是没有跑满的,达到了预计效果。

2)  不限制cpu的使用核数

package main

import (
"fmt"
"runtime"
"time"
) func main() {
cpu := runtime.NumCPU()
//不限制CPU使用个数 for i := ; i < ; i++ { //起8个goroutine,每个goroutine来跑一个无限循环的匿名函数
go func() {
for { }
}()
}
fmt.Printf("%d\n", cpu)
time.Sleep( * time.Second)
}

执行结果:

CPU示意图:

我们可以发现CPU已将完全跑满了。

总结:

我们日后使用时,如果只是一个监控、收集日志小程序,要适当控制一下CPU使用个数,如果不控制的话,程序出现bug,而且把机器核数也跑满了,最后连业务程序也跑不了了。

Go语言基础之11--Goroutine的更多相关文章

  1. Coursera课程笔记----计算导论与C语言基础----Week 11

    C程序中的字符串(Week 11) 字符数组 所有的字符串,都是以\0结尾的 只能在数组定义并初始化的时候:char c[6] = "China"; 不能用赋值语句将一个字符串常量 ...

  2. JavaScript语言基础知识11

    JavaScript字符的比较. 在接下来的学习内容的开始,我们先来看一下alert()此功能,它是一个消息框. OK,接下来正式介绍代码: <HTML> <HEAD> < ...

  3. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  4. GO学习-(11) Go语言基础之map

    Go语言基础之map Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现. map map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能 ...

  5. 【GoLang】GO语言系列--002.GO语言基础

    002.GO语言基础 1 参考资料 1.1 http://www.cnblogs.com/vimsk/archive/2012/11/03/2736179.html 1.2 https://githu ...

  6. Go语言基础之函数

    Go语言基础之函数 函数是组织好的.可重复使用的.用于执行指定任务的代码块.本文介绍了Go语言中函数的相关内容. 函数 Go语言中支持函数.匿名函数和闭包,并且函数在Go语言中属于“一等公民”. 函数 ...

  7. GO学习-(12) Go语言基础之函数

    Go语言基础之函数 函数是组织好的.可重复使用的.用于执行指定任务的代码块.本文介绍了Go语言中函数的相关内容. 函数 Go语言中支持函数.匿名函数和闭包,并且函数在Go语言中属于"一等公民 ...

  8. C语言基础回顾

    第一章 C语言基础 1.  C语言编译过程 预处理:宏替换.条件编译.头文件包含.特殊符号 编译.优化:翻译并优化成等价的中间代码表示或汇编代码 汇编:生成目标文件,及与源程序等效的目标的机器语言代码 ...

  9. R语言基础:数组&列表&向量&矩阵&因子&数据框

    R语言基础:数组和列表 数组(array) 一维数据是向量,二维数据是矩阵,数组是向量和矩阵的直接推广,是由三维或三维以上的数据构成的. 数组函数是array(),语法是:array(dadta, d ...

  10. JavaScript基础---语言基础(1)

    写在前面: 通过四篇博客把JS基础中的基础整理一下,方便自己查阅,这些内容对于实际项目开发中也许并不会在意,但是作为JS的语言基础,自觉还是应该熟悉.在完成这三篇博客(JavaScript基础---语 ...

随机推荐

  1. import requests

  2. [Elasticsearch2.x] 多字段搜索 (一) - 多个及单个查询字符串 <译>

    多字段搜索(Multifield Search) 本文翻译自官方指南的Multifield Search一章. 查询很少是只拥有一个match查询子句的查询.我们经常需要对一个或者多个字段使用相同或者 ...

  3. Java 访问 Kylin 总结

    这次开发功能是OEM统计报表.统计报表的数据由大数据平台部的同事收集,数据的展示由我们部门开发. 大数据那边使用 Kylin 分布式分析引擎(kylin官方文档). Kylin 虽比较偏向大数据相关, ...

  4. 生产者与消费者-1:N-基于list

    一个生产者/多个消费者: /** * 生产者 */ public class P { private MyStack stack; public P(MyStack stack) { this.sta ...

  5. UltraISO制作系统ISO镜像

    一.简介 UltraISO是一款功能强大而又方便实用的光盘映像文件制作/编辑/转换工具,它可以直接编辑ISO文件和从ISO中提取文件和目录,也可以从CD-ROM制作光盘映像或者将硬盘上的文件制作成IS ...

  6. 数字图像处理实验(5):PROJECT 04-01 [Multiple Uses],Two-Dimensional Fast Fourier Transform 标签: 图像处理MATLAB数字图像处理

    实验要求: Objective: To further understand the well-known algorithm Fast Fourier Transform (FFT) and ver ...

  7. R: 用 R 查看、管理文件(夹)

    文件管理主要函数: list.files( ): 查看当前目录下文件. file.show( ): 显示文件. file.access( ): 查看文件是否可读可写. file.create( ): ...

  8. ZROI2018提高day3t1

    传送门 分析 我们可以用贪心的思想.对于所有并没有指明关系的数一定是将小的放在前面.于是我们按顺序在每一个已经指明大小顺序的数前面插入所有比它小且没有指明关系的数.详见代码. 代码 #include& ...

  9. Batch Normalization 与Dropout 的冲突

    BN或Dropout单独使用能加速训练速度并且避免过拟合 但是倘若一起使用,会产生负面效果. BN在某些情况下会削弱Dropout的效果 对此,BN与Dropout最好不要一起用,若一定要一起用,有2 ...

  10. malloc/free 与 new/delete同与不同

    一.相同点 1.都是从堆上申请内存,由程序员来掌控这段内存的申请与释放. 2.对于内置类型,两者使用没有太大区别. 二.不同点 1.malloc/free是C++/C语言的标准库函数,需要库支持:ne ...