关于golang高并发的实现与注意事项说明


Posted in Golang onMay 08, 2021

一、并发的意义

并发的意义就是让 一个程序同时做多件事情,其目的只是为了能让程序同时做另一件事情而已,而不是为了让程序运行的更快(如果是多核处理器,而且任务可以分成相互独立的部分,那么并发确实可以让事情解决的更快)。

golang从语言级别上对并发提供了支持,而且在启动并发的方式上直接添加了语言级的关键字,不必非要按照固定的格式来定义线程函数,也不必因为启动线程的时候只能给线程函数传递一个参数而烦恼。

二、并发的启动

go的并发启动非常简单,几乎没有什么额外的准备工作,要并发的函数和一般的函数没有什么区别,参数随意,启动的时候只需要加一个go关键之即可,其最精髓的部分在于这些协程(协程类似于线程,但是是更轻量的线程)的调度。

package main
 
import (
 "fmt"
 "time"
)
 
func comFunc() {
 fmt.Println("This is a common function.")
}
 
func main() {
 go comFunc()
 time.Sleep(time.Second * 3)
}

三、协程间的同步与通信

1、sync.WaitGroup

sync包中的WaitGroup实现了一个类似任务队列的结构,你可以向队列中加入任务,任务完成后就把任务从队列中移除,如果队列中的任务没有全部完成,队列就会触发阻塞以阻止程序继续运行,具体用法参考如下代码:

package main
import (
 "fmt"
 "sync"
)
var waitGroup sync.WaitGroup
func Afunction(index int) {
 fmt.Println(index)
 waitGroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}
 
func main() {
 for i := 0; i < 10; i++ {
  waitGroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
  go Afunction(i)
 }
 waitGroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
}

2、channel

channel是一种golang内置的类型,英语的直译为"通道",其实,它真的就是一根管道,而且是一个先进先出的数据结构。

我们能对channel进行的操作只有4种:

(1) 创建chennel (通过make()函数)

(2) 放入数据 (通过 channel <- data 操作)

(3) 取出数据 (通过 <-channel 操作)

(4) 关闭channel (通过close()函数)

channel的3种性质入如下:

(1) channel是一种自动阻塞的管道。

如果管道满了,一个对channel放入数据的操作就会阻塞,直到有某个routine从channel中取出数据,这个放入数据的操作才会执行。相反同理,如果管道是空的,一个从channel取出数据的操作就会阻塞,直到某个routine向这个channel中放入数据,这个取出数据的操作才会执行。这是channel最重要的一个性质!!!

package main
func main() {
 ch := make(chan int, 3)
 ch <- 1
 ch <- 1
 ch <- 1
 ch <- 1 //这一行操作就会发生阻塞,因为前三行的放入数据的操作已经把channel填满了
}
package main
func main() {
 ch := make(chan int, 3)
 <-ch //这一行会发生阻塞,因为channel才刚创建,是空的,没有东西可以取出
}

(2)channel分为有缓冲的channel和无缓冲的channel。

两种channel的创建方法如下:

ch := make(chan int)  //无缓冲的channel,同等于make(chan int, 0)
ch := make(chan int, 5) //一个缓冲区大小为5的channel

无缓冲通道与有缓冲通道的主要区别为:无缓冲通道存取数据是同步的,即如果通道中无数据,则通道一直处于阻塞状态;有缓冲通道存取数据是异步的,即存取数据互不干扰,只有当通道中已满时,存数据操作,通道阻塞;当通道中为空时,取数据操作,通道阻塞。

因此,使用无缓冲的channel时,放入操作和取出操作不能在同一个routine中,而且应该是先确保有某个routine对它执行取出操作,然后才能在另一个routine中执行放入操作,否则会发生死锁现象,示例如下:

package main 
import (
 "fmt"
 "sync"
)
 
var waitGroup sync.WaitGroup //使用wg等待所有routine执行完毕,并输出相应的提示信息
 
func AFunc(ch chan int) {
 waitGroup.Add(1)
FLAG:
 for {
  select {
  case val := <-ch:
   fmt.Println(val)
   break FLAG
  }
 }
 waitGroup.Done()
 fmt.Println("WaitGroup Done")
}
 
func main() {
 
 ch := make(chan int) //无缓冲通道
 execMode := 0        //执行模式 0:先启动并发,正常输出100 1:后启动并发,发生死锁
 switch execMode {
 case 0:
  go AFunc(ch)
  ch <- 100
 case 1:
  ch <- 100
  go AFunc(ch)
 }
 waitGroup.Wait()
 close(ch)
}

使用带缓冲的channel时,因为有缓冲空间,所以只要缓冲区不满,放入操作就不会阻塞,同样,只要缓冲区不空,取出操作就不会阻塞。

而且,带有缓冲的channel的放入和取出操作可以用在同一个routine中。

但是,一定要注意放入和取出的速率问题,否则也会发生死锁现象,示例如下:

package main
import (
 "fmt"
 "sync"
)
var waitGroup sync.WaitGroup
func AFunc(ch chan int, putMode int) {
 val := <-ch
 switch putMode {
 case 0:
  fmt.Printf("Vaule=%d\n", val)
 case 1:
  fmt.Printf("Vaule=%d\n", val)
  for i := 1; i <= 5; i++ {
   ch <- i * val
  }
 case 2:
  fmt.Printf("Vaule=%d\n", val)
  for i := 1; i <= 5; i++ {
   <-ch
  }
 }
 
 waitGroup.Done()
 fmt.Println("WaitGroup Done", val)
}
 
func main() {
 ch := make(chan int, 10)
 putMode := 0 //该模式下,能够正常输出所有数据
 //putMode := 1//当放入速度远大于取数速度时,程序阻塞
 //putMode := 2//当取数速度远大于放数速度时,程序阻塞
 for i := 0; i < 1000; i++ {
  ch <- i
  waitGroup.Add(1)
  go AFunc(ch, putMode)
 }
 waitGroup.Wait()
 close(ch)
}

(3)关闭后的channel可以取数据,但是不能放数据。

而且,channel在执行了close()后并没有真的关闭,channel中的数据全部取走之后才会真正关闭。

package main
func main() {
 ch := make(chan int, 5)
 ch <- 1
 ch <- 1
 close(ch)
 ch <- 1 //不能对关闭的channel执行放入操作
        
        // 会触发panic
}
package main
func main() {
 ch := make(chan int, 5)
 ch <- 1
 ch <- 1
 close(ch)
 <-ch //只要channel还有数据,就可能执行取出操作
 
        //正常结束
}
package main 
import "fmt" 
func main() {
 ch := make(chan int, 5)
 ch <- 1
 ch <- 1
 ch <- 1
 ch <- 1
 close(ch)  //如果执行了close()就立即关闭channel的话,下面的循环就不会有任何输出了
 for {
  data, ok := <-ch
  if !ok {
   break
  }
  fmt.Println(data)
 }
 
 // 输出:
 // 1
 // 1
 // 1
 // 1
 // 
 // 调用了close()后,只有channel为空时,channel才会真的关闭
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
go语言-在mac下brew升级golang
Apr 25 Golang
golang在GRPC中设置client的超时时间
Apr 27 Golang
Go 实现英尺和米的简单单位换算方式
Apr 29 Golang
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
May 05 Golang
Go timer如何调度
Jun 09 Golang
再次探讨go实现无限 buffer 的 channel方法
Jun 13 Golang
深入理解go缓存库freecache的使用
Feb 15 Golang
简单聊聊Golang中defer预计算参数
Mar 25 Golang
Go获取两个时区的时间差
Apr 20 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 Golang
Golang并发工具Singleflight
May 06 Golang
Go语言怎么使用变长参数函数
Jul 15 Golang
基于Golang 高并发问题的解决方案
May 08 #Golang
使用golang编写一个并发工作队列
May 08 #Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 #Golang
golang 实现时间戳和时间的转化
May 07 #Golang
Golang Gob编码(gob包的使用详解)
May 07 #Golang
go mod 安装依赖 unkown revision问题的解决方案
解决golang 关于全局变量的坑
May 06 #Golang
You might like
php实现的在线人员函数库
2008/04/09 PHP
php $_SERVER[&quot;REQUEST_URI&quot;]获取值的通用解决方法
2010/06/21 PHP
一个简单的jQuery插件制作 学习过程及实例
2010/04/25 Javascript
鼠标滑上去后图片放大浮出效果的js代码
2011/05/28 Javascript
JavaScript高级程序设计(第3版)学习笔记5 js语句
2012/10/11 Javascript
Javascript中判断变量是数组还是对象(array还是object)
2013/08/14 Javascript
javascript按位非运算符的使用方法
2013/11/14 Javascript
jQuery的one()方法用法实例
2015/01/19 Javascript
JavaScript随机生成信用卡卡号的方法
2015/04/07 Javascript
基于Echarts 3.19 制作常用的图形(非静态)
2016/05/19 Javascript
jQuery表单验证插件解析(推荐)
2016/07/21 Javascript
JQuery和html+css实现带小圆点和左右按钮的轮播图实例
2017/07/22 jQuery
用react-redux实现react组件之间数据共享的方法
2018/06/08 Javascript
vue通过style或者class改变样式的实例代码
2018/10/30 Javascript
玩转Koa之koa-router原理解析
2018/12/29 Javascript
微信小程序实现单选选项卡切换效果
2020/06/19 Javascript
js中比较两个对象是否相同的方法示例
2019/09/02 Javascript
elementUI同一页面展示多个Dialog的实现
2020/11/19 Javascript
使用Python的urllib2模块处理url和图片的技巧两则
2016/02/18 Python
python入门前的第一课 python怎样入门
2018/03/06 Python
对numpy中布尔型数组的处理方法详解
2018/04/17 Python
python 多线程中子线程和主线程相互通信方法
2018/11/09 Python
Python利用requests模块下载图片实例代码
2019/08/12 Python
Python实现桌面翻译工具【新手必学】
2020/02/12 Python
python GUI库图形界面开发之PyQt5布局控件QGridLayout详细使用方法与实例
2020/03/06 Python
Html5大文件断点续传实现方法
2015/12/05 HTML / CSS
大学生党课思想汇报
2013/12/29 职场文书
父亲生日宴会答谢词
2014/01/10 职场文书
个人实习生的自我评价
2014/02/16 职场文书
房地产广告词大全
2014/03/19 职场文书
感恩节活动策划方案
2014/05/16 职场文书
医院护士见习期自我鉴定
2014/09/15 职场文书
教师党员批评与自我批评
2014/10/15 职场文书
2015年乡镇人大工作总结
2015/04/22 职场文书
公司酒会主持词
2015/07/02 职场文书
手残删除python之后的补救方法
2021/06/26 Python