关于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 相关文章推荐
用golang如何替换某个文件中的字符串
Apr 25 Golang
Golang 实现超大文件读取的两种方法
Apr 27 Golang
解决golang在import自己的包报错的问题
Apr 29 Golang
Golang之sync.Pool使用详解
May 06 Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 Golang
golang 实用库gotable的具体使用
Jul 01 Golang
K8s部署发布Golang应用程序的实现方法
Jul 16 Golang
go开发alertmanger实现钉钉报警
Jul 16 Golang
Go中的条件语句Switch示例详解
Aug 23 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
victoriaMetrics库布隆过滤器初始化及使用详解
Apr 05 Golang
Golang 遍历二叉树
Apr 19 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
全国FM电台频率大全 - 23 四川省
2020/03/11 无线电
asp和php下textarea提交大量数据发生丢失的解决方法
2008/01/20 PHP
PHP访问MYSQL数据库封装类(附函数说明)
2010/12/04 PHP
PHP错误和异长常处理总结
2014/03/06 PHP
Codeigniter整合Tank Auth权限类库详解
2014/06/12 PHP
PHP加密解密实例分析
2015/12/25 PHP
PHP实现的浏览器检查类
2016/04/11 PHP
PHP实现打包下载文件的方法示例
2017/10/07 PHP
jQuery Ajax 全解析
2009/02/08 Javascript
jQuery实现瀑布流布局
2014/12/12 Javascript
jQuery搜索同辈元素方法
2015/02/10 Javascript
JavaScript实现表格点击排序的方法
2015/05/11 Javascript
JavaScript中split与join函数的进阶使用技巧
2016/05/03 Javascript
JS简单判断函数是否存在的方法
2017/02/13 Javascript
基于nodejs res.end和res.send的区别
2018/05/14 NodeJs
详解iview的checkbox多选框全选时校验问题
2019/06/10 Javascript
ES5新增数组的实现方法
2020/05/12 Javascript
[00:32]2018DOTA2亚洲邀请赛EG出场
2018/04/03 DOTA
[02:03]完美世界DOTA2联赛10月30日赛事集锦
2020/10/31 DOTA
Python Tkinter基础控件用法
2014/09/03 Python
使用Python对SQLite数据库操作
2017/04/06 Python
详解 Python 与文件对象共事的实例
2017/09/11 Python
python flask框架实现传数据到js的方法分析
2019/06/11 Python
Python阶乘求和的代码详解
2020/02/14 Python
SEPHORA新西兰官方网站:购买化妆品和护肤品
2016/12/02 全球购物
美国户外生活方式品牌:Eddie Bauer
2016/12/28 全球购物
DeinDesign德国:设计自己的手机壳
2019/12/14 全球购物
沙特阿拉伯排名第一的在线时尚购物应用程序:1Zillion
2020/08/08 全球购物
中职生自荐信范文
2014/06/15 职场文书
群众路线四风问题整改措施
2014/09/27 职场文书
2014年房地产工作总结范文
2014/11/19 职场文书
成品仓库管理员岗位职责
2015/04/09 职场文书
2015年教研工作总结
2015/05/23 职场文书
幼儿园托班教育随笔
2015/08/14 职场文书
python使用tkinter实现透明窗体上绘制随机出现的小球(实例代码)
2021/05/17 Python
Python学习之迭代器详解
2022/04/01 Python