关于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 相关文章推荐
goland 恢复已更改文件的操作
Apr 28 Golang
浅谈golang 中time.After释放的问题
May 05 Golang
Golang实现AES对称加密的过程详解
May 20 Golang
详解Go语言Slice作为函数参数的使用
Jul 02 Golang
Go 语言结构实例分析
Jul 04 Golang
go开发alertmanger实现钉钉报警
Jul 16 Golang
Go 中的空白标识符下划线
Mar 25 Golang
victoriaMetrics库布隆过滤器初始化及使用详解
Apr 05 Golang
Go归并排序算法的实现方法
Apr 06 Golang
golang语言指针操作
Apr 14 Golang
golang用type-switch判断interface的实际存储类型
Apr 14 Golang
Go调用Rust方法及外部函数接口前置
Jun 14 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 adodb连接mssql解决乱码问题
2009/06/12 PHP
php循环table实现一行两列显示的方法
2015/06/04 PHP
php while循环控制的简单实例
2016/05/30 PHP
php字符串比较函数用法小结(strcmp,strcasecmp,strnatcmp及strnatcasecmp)
2016/07/18 PHP
php对微信支付回调处理的方法
2018/08/23 PHP
JavaScript 学习技巧
2010/02/17 Javascript
优化Jquery,提升网页加载速度
2013/11/14 Javascript
js确认删除对话框适用于a标签及submit
2014/07/10 Javascript
分享20个提升网站界面体验的jQuery插件
2014/12/15 Javascript
jQuery选择器源码解读(五):tokenize的解析过程
2015/03/31 Javascript
Bootstrap Chart组件使用教程
2016/04/28 Javascript
JS图片定时翻滚效果实现方法
2016/06/21 Javascript
H5基于iScroll实现下拉刷新和上拉加载更多
2017/07/18 Javascript
vue.js实现的绑定class操作示例
2018/07/06 Javascript
更改BootStrap popover的默认样式及popover简单用法
2018/09/13 Javascript
Vue+Express实现登录注销功能的实例代码
2019/05/05 Javascript
Vue 实现简易多行滚动&quot;弹幕&quot;效果
2020/01/02 Javascript
python利用不到一百行代码实现一个小siri
2017/03/02 Python
Python编程深度学习计算库之numpy
2018/12/28 Python
详解python函数的闭包问题(内部函数与外部函数详述)
2019/05/17 Python
Python多进程入门、分布式进程数据共享实例详解
2019/06/03 Python
Django实现将一个字典传到前端显示出来
2020/04/03 Python
python中加背景音乐如何操作
2020/07/19 Python
Python环境配置实现pip加速过程解析
2020/11/27 Python
Python之多进程与多线程的使用
2021/02/23 Python
HTML5 FormData 方法介绍以及实现文件上传示例
2017/09/12 HTML / CSS
详解HTML5中ol标签的用法
2015/09/08 HTML / CSS
澳大利亚排名第一的在线酒类商店:MyBottleShop
2018/04/26 全球购物
怎么处理XML的中文问题
2015/03/26 面试题
行政办公员自我评价分享
2013/12/14 职场文书
求职简历中个人的自我评价
2013/12/25 职场文书
房产协议书范本2014
2014/09/30 职场文书
先进集体申报材料
2014/12/25 职场文书
2015年派出所民警工作总结
2015/04/24 职场文书
SpringCloud Feign请求头删除修改的操作代码
2022/03/20 Java/Android
gtx1650怎么样 gtx1650显卡相当于什么级别
2022/04/08 数码科技