GO_11:GO语言基础之并发concurrency
并发Concurrency
很多人都是冲着 Go 大肆宣扬的高并发而忍不住跃跃欲试,但其实从源码的解析来看,goroutine 只是由官方实现的超级“线程池”而已。不过话说回来,每个实例 4~5KB的栈内存占用和由于实现机制而大幅减少的创建和销毁开销,是制造 Go 号称的高并发的根本原因。另外,goroutine 的简单易用,也在语言层面上给予了开发者巨大的遍历。
高并发当中一定要注意:并发可不是并行。
并发主要由切换时间片来实现“同时”运行,而并行则是直接利用多核实现多线程的运行,但 Go 可以设置使用核数,以发挥多核计算机的处理能力。
goroutine 奉行通过通信来共享内存,而不是共享内存来通信。Go 语言主要是通过 Channe 技术通信来实现内存的共享的,因为 channel 是一个通道,Go 是通过通道来通信进行内存数据的共享。
对于初学者,goroutine直接理解成为线程就可以了。当对一个函数调用go,启动一个goroutine的时候,就相当于起来一个线程,执行这个函数。
实际上,一个goroutine并不相当于一个线程,goroutine的出现正是为了替代原来的线程概念成为最小的调度单位。一旦运行goroutine时,先去当先线程查找,如果线程阻塞了,则被分配到空闲的线程,如果没有空闲的线程,那么就会新建一个线程。注意的是,当goroutine执行完毕后,线程不会回收推出,而是成为了空闲的线程。
让我们先来看一个最简单的 goroutine 案例:
package main import (
"fmt"
"time"
) func main() {
//启用一个goroutine
go GoRun()
//这里加一个休眠是因为主线程已启动就执行完毕消亡来,子线程还来不及执行
time.Sleep( * time.Second)
} func GoRun() {
fmt.Println("Go Go Go!!!")
}
运行结果:
Go Go Go!!!
Channel
1. Channel 是 goroutine 沟通的桥梁,大都是阻塞同步的
2. 它是通过 make 创建,close 关闭
3. Channel 是引用类型
4. 可以使用 for range 来迭代,不断操作 channel
5. 可以设置单向 或 双向通道
6. 可以设置缓存大小,在未被填满前不会发生阻塞,即它是异步的
那么针对上溯代码我们不使用休眠,而使用 Channel 来实现我们想要的效果:
channel的意思用白话可以这么理解:主线程告诉大家你开goroutine可以,但是我在我的主线程开了一个管道,你做完了你要做的事情之后,往管道里面塞个东西告诉我你已经完成了。
package main import (
"fmt"
) func main() {
//声明创建一个通道,存储类型为bool型
c := make(chan bool)
//启用一个goroutine,使用的是匿名方法方式
go func() {
fmt.Println("Go Go Go!!!")
c <- true //向 channel 中存入一个值
}()
//当程序执行完毕之后再从通道中取出刚才赋的值
<- c
/**
主线程启动了一个匿名子线程后就执行到了:<-c , 到达这里主线程就被阻塞了。只有当子线程向通道放入值后主线程阻塞才会被释放
其实这个就是完成了消息的发送
*/
}
上溯代码可以修改为使用 for range 来进行消息的发送:
package main import (
"fmt"
) func main() {
//声明创建一个通道,存储类型为bool型,这里设置的channel就是双向通道,既可以存也可以取
c := make(chan bool)
//启用一个goroutine,使用的是匿名方法方式
go func() {
fmt.Println("Go Go Go!!!")
c <- true //向 channel 中存入一个值
close(c) //切记如果使用for range来进行取值的时候需要在某个地方进行关闭,否则会发生死锁
}()
//从通道中循环取出刚才赋的值
for v := range c {
fmt.Println(v)
}
}
从以上代码可以看出,一般使用的 Channel 都是双向通道的,即:既可以取又可以存。那单向通道一般用于什么场景下呢?
单向通道又分为两种,一种是只能读取,一种是只能存放,一般用于参数类型传递使用。例如有个方法返回一个Channel类型,一般要求操作只能从这里取,那么此时它的用途就是只能存放类型,如果此时你不小心存数据,此时会发生panic 导致程序奔溃发生异常。那么读取类型的Channel同理。这样做其实也是为了程序的安全性与健壮性,防止一些误操作。
这里还有一个知识点,就是有缓存的channel 和 无缓存的channel的区别?
make(chan bool, 1) 表示带有一个缓存大小的缓存channel
make(chan bool) 或 make(chan bool, 0) 表示一个无缓存的channel
无缓存channel是阻塞的即同步的,而有缓存channel是异步的。怎么说?比如
c1:=make(chan int) 无缓冲
c2:=make(chan int,1) 有缓冲
c1 <- 1 //往无缓存通道放入数据 1
无缓冲的 不仅仅是 向 c1 通道放 1 而且一定要有别的线程 <- c1 接手了这个参数,那么 c1 <- 1 才会继续下去,要不然就一直阻塞着
而 c2 <- 1 则不会阻塞,因为缓冲大小是1 只有当放第二个值的时候第一个还没被人拿走,这时候才会阻塞。
打个比喻
无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。
无缓冲保证信能到你手上
有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。
有缓冲的 保证 信能进你家的邮箱
那如果在多线程环境下,多个线程并发抢占会使得打印不是按照顺序来,那么我们如何确保子线程全部结束完之后主线程再停止呢?主要有两种方式:
第一种:使用阻塞channel
package main import (
"fmt"
"runtime"
) func main() {
fmt.Println("当前系统核数:", runtime.NumCPU())
runtime.GOMAXPROCS(runtime.NumCPU()) //设置当前程序执行使用的并发数
//定义一个阻塞channel
c := make(chan bool)
//这里启动10个线程运行
for i :=; i < ; i++ {
go goRun(c, i)
}
//我们知道一共有10次循环,那么在这里就取10次,那么子线程goRun只有都执行完了主线程取才能完毕,因为这里也循环取10次,不够的话会被阻塞
for i := ; i < ; i++ {
<- c
}
} func goRun(c chan bool, index int) {
a :=
//循环叠加1千万次并返回最终结果
for i := ; i < ; i++ {
a += i
}
fmt.Println("线程序号:", index, a)
//往阻塞队列插入内容
c <- true
}
打印结果:
当前系统核数: 4
线程序号: 9 49999995000001
线程序号: 5 49999995000001
线程序号: 2 49999995000001
线程序号: 0 49999995000001
线程序号: 6 49999995000001
线程序号: 1 49999995000001
线程序号: 3 49999995000001
线程序号: 7 49999995000001
线程序号: 8 49999995000001
线程序号: 4 49999995000001
从打印结果可以看出,多线程环境下运行代码打印和顺序没有关系,由 CPU 调度自己决定,多运行几次打印结果一定不会一样,就是这个道理。
第二种:使用同步机制
package main import (
"fmt"
"runtime"
"sync"
) func main() {
fmt.Println("当前系统核数:", runtime.NumCPU())
runtime.GOMAXPROCS(runtime.NumCPU()) //设置当前程序执行使用的并发数
/**
waitGroup即任务组,它的最要作用就是用来添加需要工作的任务,没完成一次任务就标记一次Done,这样任务组的待完成量会随之减1
那么主线程就是来判断任务组内是否还有未完成任务,当没有未完成当任务之后主线程就可以结束运行,从而实现了与阻塞队列类似的同步功能
这里创建了一个空的waitGroup(任务组)
*/
wg := sync.WaitGroup{}
wg.Add() //添加10个任务到任务组中
//这里启动10个线程运行
for i :=; i < ; i++ {
go goRun(&wg, i)
}
wg.Wait()
} /**
这里需要传入引用类型不能传入值拷贝,因为在子线程中是需要执行Done操作,类似与我们修改结构体中的int变量主词递减,如果是只拷贝的话是不会影响原类型内的数据
这样就会发生死循环导致死锁程序奔溃,报错异常为:fatal error: all goroutines are asleep - deadlock!
*/
func goRun(wg *sync.WaitGroup, index int) {
a :=
//循环叠加1千万次并返回最终结果
for i := ; i < ; i++ {
a += i
}
fmt.Println("线程序号:", index, a) wg.Done()
}
打印结果:
当前系统核数: 4
线程序号: 1 49999995000001
线程序号: 5 49999995000001
线程序号: 0 49999995000001
线程序号: 9 49999995000001
线程序号: 4 49999995000001
线程序号: 3 49999995000001
线程序号: 2 49999995000001
线程序号: 6 49999995000001
线程序号: 8 49999995000001
线程序号: 7 49999995000001
以上所有讲解到的都是基于一个 channel 来说的,那么当我们有多个 channel 的时候又该怎么处理呢?
Go 语言为我们提供了一种结构名为:Select,它和 switch 是非常相似的,switch 主要用于普通类型做判断的,而 select 主要是针对多个 channel 来进行判断的。
Select
1. 可处理一个或多个 channel 的发送与接收
2. 同时有多个可用的 channel 时,可以按随机顺序处理
3. 可以使用空的 select 来阻塞 main 函数
4. 它还可以设置超时时间
案例一:用多个 channel 来接收数据:
package main import (
"fmt"
) /**
数据接收处理
*/
func main() {
//批量初始化channel
c1, c2 := make(chan int), make(chan string)
//创建一个启动goroutine的匿名函数
go func() {
/**
创建一个无限循环语句,使用select进行处理
我们一般都是使用这种方式来处理不断的消息发送和处理
*/
for {
select {
case v, ok := <- c1:
if !ok {
break
}
fmt.Println("c1:", v)
case v, ok := <- c2:
if !ok {
break
}
fmt.Println("c2:", v)
}
}
}() c1 <-
c2 <- "liang"
c1 <-
c2 <- "xuli" //关闭channel
close(c1)
close(c2)
}
打印结果:
c1: 1
c2: liang
c1: 2
c2: xuli
案例二:用多个 channel 来发送数据:
package main import (
"fmt"
) /**
数据接收处理,这里实现随机接收0、1 数字并打印
*/
func main() {
c := make(chan int)
num :=
//创建一个启动goroutine的匿名函数
go func() {
for v := range c {
num++
if num & == {
fmt.Println()
}
fmt.Print(v, " ")
}
}() for {
select {
case c <- :
case c <- :
}
}
}
打印结果:(只是粘贴了其中一部分)
1 1 0 1 1 0 0 0 0 1 0 1 0 0 1 0
0 1 0 1 1 0 1 1 0 0 1 1 1 0 0 1
1 1 1 1 0 0 1 1 0 0 0 0 0 1 0 1
0 1 1 0 0 0 1 1 1 0 0 0 1 1 0 0
1 1 1 0 0 0 0 0 1 0 1 1 1 1 1 1
0 0 1 0 0 0 0 1 0 1 1 0 1 1 1 0
1 1 1 0 0 0 1 1 1 0 0 0 1 0 0 1
1 0 1 1 1 1 0 0 1 0 0 1 1 1 1 1
1 1 0 0 0 0 0 1 1 1 0 1 1 0 1 1
1 1 0 0 0 0 1 0 0 1 0 1 0 0 1 1
0 0 0 1 1 1 1 1 0 0 0 1 0 0 0 1
0 1 1 0 1 0 1 0 1 0 0 1 1 0 0 0
0 1 0 0 0 1 0 0 0 1 1 0 0 0 1 1
1 1 0 1 1 1 1 0 0 0 1 0 0 0 1 1
0 1 1 0 0 1 1 0 1 0 1 0 0 0 0 1
0 1 1 0 0 0 1 1 0 1 0 1 0 0 0 0
0 0 0 1 0 0 0 1 1 1 1 1 1 1 1 0
案例三:用 channel 设置超时时间:
package main import (
"fmt"
"time"
) /**
select的超时应用
*/
func main() {
c := make(chan bool)
select {
case v := <- c :
fmt.Println(v)
case <- time.After( * time.Second):
fmt.Println("TimeOut!!!")
}
}
打印结果:
TimeOut!!!
GO_11:GO语言基础之并发concurrency的更多相关文章
- GO语言基础之并发concurrency
		
并发Concurrency 很多人都是冲着 Go 大肆宣扬的高并发而忍不住跃跃欲试,但其实从源码的解析来看,goroutine 只是由官方实现的超级“线程池”而已.不过话说回来,每个实例 4-5KB的 ...
 - GO学习-(18) Go语言基础之并发
		
Go语言基础之并发 并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微 ...
 - Go语言基础之并发
		
并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天) ...
 - 《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(下)
		
索引: 一.SQL Server的体系结构 二.查询 三.表表达式 四.集合运算 五.透视.逆透视及分组 六.数据修改 七.事务和并发 八.可编程对象 五.透视.逆透视及分组 5.1 透视 所谓透视( ...
 - 《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(上)
		
索引: 一.SQL Server的体系结构 二.查询 三.表表达式 四.集合运算 五.透视.逆透视及分组 六.数据修改 七.事务和并发 八.可编程对象 一.SQL Server体系结构 1.1 数据库 ...
 - 并发(Concurrency)和并行(Parallelism)的区别
		
最近在读<real world haskell>里关于并行的一章时,看到作者首先对并发(Concurrency)和并行(Parallelism)的区别进行了定义和解释.以前我对这个问题也是 ...
 - 我的学习目标(目前已初步学习完Java语言基础)
		
操作系统.尤其是内存/线程/进程方面 计算机网络协议,重点关注 TCP/UDP/HTTP. 数据结构与算法. 数据库 设计模式,熟练掌握常用的几种设计模式. Java语言基础.熟悉java语言基础,了 ...
 - Golang 并发concurrency
		
并发concurrency 很多人都是冲着Go大肆宣扬的高并发而忍不住跃跃欲试,但其实从源码解析来看,goroutine只是由官方实现的超级"线程池"而已.不过话说回来,每个实例4 ...
 - D10——C语言基础学PYTHON
		
C语言基础学习PYTHON——基础学习D10 20180906内容纲要: 1.协程 (1)yield (2)greenlet (3)gevent (4)gevent实现单线程下socket多并发 2. ...
 
随机推荐
- Scrum Meeting 5 -2014.11.11
			
放假过掉一大半.大家都努力赶着进度,算法实现基本完成.可能还有些细小的改动,但也可以统一进入测试阶段了. 今天叫了部分在校人员开了个小会.任务决定以测试为主,同时开始进行服务器的部署. 在之前尝试服务 ...
 - 团队博客作业Week3 --- 项目选择&&需求疑问
			
项目选择 经过团队内所有成员一致探讨,我们团队选择完善和改进之学霸系统的第二个子模块,即:网站内容结构定义和数据处理.具体的要求如下:(摘自Xueba系统项目需求) 网站内容结构定义和数据处理(Con ...
 - 2-Seventh Scrum Meeting20151207
			
任务分配 闫昊: 今日完成:完成数据库设计. 明日任务:和唐彬讨论接口如何在android实现. 唐彬: 今日完成:读了IOS讨论区后台接口. 明日任务:和闫昊讨论接口如何在android实现. 史烨 ...
 - 第二阶段Sprint6
			
昨天:设置统一保存路径为内存卡,实现可以选择播放已有的视频 今天:将“录制”及“保存”整合到一起,修复出现的Bug,使之能够正常运行. 遇到的问题:感觉调的摄像头录制的画面不好,这怎么办啊?
 - java List.get
			
并不能 用如果List在i位置值不存在 并不能 List.get(i) !=null 判断 会抛异常 版权声明:本文为博主原创文章,未经博主允许不得转载.
 - python learning OOP2.py
			
class Student(object): pass s = Student() s.name = 'Chang' # 给一个实例动态绑定一个属性 print(s.name) def set_age ...
 - 网络1711-12&信管1711-12 图 作业评分
			
先放上本次作业的推荐博客,以及评分细则总表,在最后,会放几张图表对本学期大家的成果进行一个小小的总结,有兴趣的同学可以看看,感受一下自己这个学期的积累和进步.(主要针对网络的同学,信管的同学只有两次作 ...
 - 对于Redis的了解
			
Redis :高性能的key-value数据库,支持存储的value类型包括字符串.链表.集合.有序集合.哈希类型. redis使用两种文件格式:全量数据和增量请求. 全量数据格式是将内存中的数据写入 ...
 - 29_Java_数据库_第29天(JDBC、DBUtils)_讲义
			
今日内容介绍 1.JDBC 2.DBUtils 01JDBC概念和数据库驱动程序 * A: JDBC概念和数据库驱动程序 * a: JDBC概述 * JDBC(Java Data Base Conne ...
 - Beta阶段DAY2
			
一.提供当天站立式会议照片一张 二.每个人的工作 1.讨论项目每个成员的昨天进展 刘阳航:删除多余按钮,调整界面. 林庭亦:删除麻烦的颜色设置. 郑子熙:添加新增按钮. 陈文俊:重新规划面板及功能. ...