关于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语言-为什么返回值为接口类型,却返回结构体
Apr 24 Golang
go语言-在mac下brew升级golang
Apr 25 Golang
Go语言带缓冲的通道实现
Apr 26 Golang
golang elasticsearch Client的使用详解
May 05 Golang
Golang Gob编码(gob包的使用详解)
May 07 Golang
golang中的并发和并行
May 08 Golang
go xorm框架的使用
May 22 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
深入理解go slice结构
Sep 15 Golang
Go语言入门exec的基本使用
May 20 Golang
GO中sync包自由控制并发示例详解
Aug 05 Golang
基于Python实现西西成语接龙小助手
Aug 05 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
一个高ai的分页函数和一个url函数
2006/10/09 PHP
一个很不错的PHP翻页类
2009/06/01 PHP
PHP生成UTF8文件的方法
2010/05/15 PHP
谈谈php对接芝麻信用踩的坑
2016/12/01 PHP
详解thinkphp5+swoole实现异步邮件群发(SMTP方式)
2017/10/13 PHP
PHP过滤器 filter_has_var() 函数用法实例分析
2020/04/23 PHP
Extjs学习笔记之一 初识Extjs之MessageBox
2010/01/07 Javascript
离开页面时检测表单元素是否被修改,提示保存的js代码
2010/08/25 Javascript
JavaScript的模块化:封装(闭包),继承(原型) 介绍
2013/07/22 Javascript
删除节点的jquery代码
2014/01/13 Javascript
超级简单的jquery操作表格方法
2014/12/15 Javascript
jQuery中$.click()无效问题分析
2015/01/29 Javascript
JS实现的手机端精简幻灯片效果
2016/09/05 Javascript
认识jQuery的Promise的具体使用方法
2017/10/10 jQuery
AngularJS ui-router刷新子页面路由的方法
2018/07/23 Javascript
JS使用队列对数组排列,基数排序算法示例
2019/03/02 Javascript
Python Sleep休眠函数使用简单实例
2015/02/02 Python
Python3中详解fabfile的编写
2018/06/24 Python
Python实现FTP弱口令扫描器的方法示例
2019/01/31 Python
python-Web-flask-视图内容和模板知识点西宁街
2019/08/23 Python
python实现mask矩阵示例(根据列表所给元素)
2020/07/30 Python
html5如何及时更新缓存文件(js、css或图片)
2013/06/24 HTML / CSS
让IE9以下版本的浏览器兼容HTML5的方法
2014/03/12 HTML / CSS
HTML5 video播放器全屏(fullScreen)方法实例
2015/04/24 HTML / CSS
稀有和绝版书籍:Biblio.com
2017/02/02 全球购物
在weblogic中发布ejb需涉及到哪些配置文件
2012/01/17 面试题
Java软件工程师综合面试题笔试题
2013/09/08 面试题
校园摄影活动策划方案
2014/02/05 职场文书
租房合同协议书
2014/04/09 职场文书
个人安全承诺书
2014/05/22 职场文书
2014党支部对照检查材料思想汇报
2014/10/05 职场文书
统计员岗位职责
2015/02/11 职场文书
隐形的翅膀观后感
2015/06/10 职场文书
安全生产培训心得体会
2016/01/18 职场文书
Python中的变量与常量
2021/11/11 Python
sql server偶发出现死锁的解决方法
2022/04/10 SQL Server