Golang | 并发
goroutine
协程(Coroutine)
Golang 在语言层面对并发编程进行了支持,使用了一种协程(goroutine)机制,
协程本质上是一种用户态线程,不需要操作系统来进行抢占式调度,但是又寄生于线程中,因此系统开销极小,可以有效的提高线程的任务并发性,而避免多线程的缺点。但是协程需要语言上的支持,需要用户自己实现调度器,因为在Go语言中,实现了调度器所以我们可以很方便的能过 go关键字来使用协程。
func main() {
for i := 0; i <10; i++ {
go func(i int) {
for {
fmt.Printf("Hello goroutine %d\n",i)
}
}(i)
}
time.Sleep(time.Millisecond)
}
最简单的一个并发编程小例子,并发输出一段话。
我们同时开了10个协程进行输出,每次在fmt.printf时交出控制权(不一定每次都会交出控制权),回到调度器中,再由调度器分配。
goroutine 可能切换的点
- I/O,Select
- channel
- 等待锁
- 函数调用
- runtime.Gosched()
我们看一个小例子:
func main() {
var a [10]int
for i := 0; i <10; i++ {
go func(i int) {
for {
a[i]++
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}
在这里,代码直接锁死,程序没有退出,因为在执行函数中没有协程的切换,因为 main函数也是一个协程。
如果想要程序退出,可以通过 runtime.Gosched()函数,在执行函数中添加一行。
for {
a[i]++
runtime.Gosched()
}
加上这个函数之后,代码是可以正常执行了,但是真的是正常执行吗?不一定,我们可以使用 -reac命令来看一下数据是否有冲突:

这说明数据还是有冲突的,数组a中的元素一边在做自增,一边在输出。解决这个问题,我们只能使用 channel 来解决。
Channel
Channel 中 Go语言在语言级别提供了对 goroutine 之间通信的支持,我们可以使用 channel 在两个或者多个goroutine之间进行信息传递,能过 channel 传递对像的过程和调用函数时的参数传递行为一样,可以传递普通参数和指针。
Channel 有两种模式:
var ch1 = make(chan int) // 无缓冲 channel,同步
var ch2 = make(chan int, 2) // 有缓冲 channel, 异步
无缓冲的方式,数据进入 channel 只要没有被接收,就会处在阻塞状态。
var ch1 = make(chan int) // 无缓冲 channel,同步
ch1 <- 1
ch1 <- 2
// error: all goroutines are asleep - deadlock!
fmt.Println(<-ch1)
如果想要运行,必须要再开一个协程不停的去请求数据:
var ch1 = make(chan int) // 无缓冲 channel,同步
go func() {
for {
n := <-ch1
fmt.Println(n)
}
}()
ch1 <- 1
ch1 <- 2
有缓冲的方式,只要缓冲区没有满就可以一直进数据,缓冲区在填满之后没有接收也会处理阻塞状态。
func bufferChannel() {
var ch2 = make(chan int,2)
ch2<-1
ch2<-2
fmt.Println(ch2)
// 不加这一行的话,是可以正常运行的
ch2<-3 // error: all goroutines are asleep - deadlock!
}
chaanel 指定方向
比如我现在有一个函数创建一个 channel,并且不断的需要消费channel中的数据:
func worker(ch chan int) {
for {
fmt.Printf("hello goroutine worker %d\n", <-ch)
}
}
func createWorker() chan int{
ch := make(chan int)
go worker(ch)
return ch
}
func main() {
ch := createWorker()
ch<-1
ch<-2
ch<-3
time.Sleep(time.Millisecond)
}
这个函数我是要给别人用的,但是我怎么保证使用 createWorker 函数创建的 channel 都是往里面传入数据的呢?
如果外面有人消费了这个 channel 中的数据,我们怎么限制?
这个时候,我们就可以给返回的channel 加上方向,指明这个 channel 中能往里传入数据,不能从中取数据:
func worker(ch <-chan int) {
for {
fmt.Printf("hello goroutine worker %d\n", <-ch)
}
}
func createWorker() chan<- int{
ch := make(chan int)
go worker(ch)
return ch
}
我们可以在返回 channel 的地方加上方向,指明返回的函数只能是一个往里传入数据,不能从中取数据。
并且我们还可以给专门消费的函数加上一个方向,指明这个函数只能出不能进。
channel 关闭
在使用 channel 的时候,随说我们可以等待channel中的函数使用完之后自己结束,或者等待 main 函数结束时关闭所有的 goroutine 函数,但是这样的方式显示不够优雅。
当一个数据我们明确知道他的结束时候,我们可以发送一个关闭信息给这个 channel ,当这个 channel 接收到这个信号之后,自己关闭。
// 方法一
func worker(ch <-chan int) {
for {
if c ,ok := <- ch;ok{
fmt.Printf("hello goroutine worker %d\n", c)
}else {
break
}
}
}
// 方法二
func worker(ch <-chan int) {
for c := range ch{
fmt.Printf("hello goroutine worker %d\n", c)
}
}
func main() {
ch := createWorker()
ch<-1
ch<-2
ch<-3
close(ch)
time.Sleep(time.Millisecond)
}
通过 Closeb函数,我们可以能过 channel 已经关闭,并且我们还可以通过两种方法判断通道内是否还有值。
Select
当我们在实际开发中,我们一般同时处理两个或者多个 channel 的数据,我们想要完成一个那个 channel 先来数据,我们先来处理个那 channel 怎么办呢?
此时,我们就可以使用 select 调度:
func genInt() chan int {
ch := make(chan int)
go func() {
i := 0
for {
// 随机两秒以内生成一次数据
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
ch <- i
i++
}
}()
return ch
}
func main() {
var c1 = genInt()
var c2 = genInt()
for {
select {
case n := <-c1:
fmt.Printf("server 1 generator %d\n", n)
case n := <- c2:
fmt.Printf("server 2 generator %d\n", n)
}
}
}
定时器
for {
tick := time.Tick(time.Second)
select {
case n := <-c1:
fmt.Printf("server 1 generator %d\n", n)
case n := <-c2:
fmt.Printf("server 2 generator %d\n", n)
case <-tick:
fmt.Println("定时每秒输出一次!")
}
}
超时
for {
tick := time.Tick(time.Second)
select {
case n := <-c1:
fmt.Printf("server 1 generator %d\n", n)
case n := <-c2:
fmt.Printf("server 2 generator %d\n", n)
case <-tick:
fmt.Println("定时每秒输出一次!")
case <-time.After(1300 * time.Millisecond): // 如果 1.3秒内没有数据进来,那么就输出超时
fmt.Println("timeout")
}
}
传统的并发控制
sync.Mutex
type atomicInt struct {
value int
lock sync.Mutex
}
func (a *atomicInt) increment() {
a.lock.Lock()
defer a.lock.Unlock() // 使用 defer 解锁,以防忘记
a.value++
}
func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.value)
}
sync.WaitGroup
type waitGrouInt struct {
value int
wg sync.WaitGroup
}
func (w *waitGrouInt) addInt() {
w.wg.Add(1)
w.value++
}
func main() {
var w waitGrouInt
for i := 0; i < 10; i++ {
w.addInt()
w.wg.Done()
}
w.wg.Wait()
fmt.Println(w.value)
}
关注公众号,随时获取最新资讯

细节决定成败!
个人愚见,如有不对,恳请斧正!
Golang | 并发的更多相关文章
- golang并发编程
golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止go ...
- 马蜂窝搜索基于 Golang 并发代理的一次架构升级
搜索业务是马蜂窝流量分发的重要入口.很多用户在使用马蜂窝时,都会有目的性地主动搜索与自己旅行需求相关的各种信息,衣食住行,事无巨细,从而做出最符合需求的旅行决策. 因此在马蜂窝,搜索业务交互的下游模块 ...
- Golang 并发简介
并发概要 随着多核CPU的普及, 为了更快的处理任务, 出现了各种并发编程的模型, 主要有以下几种: 模型名称 优点 缺点 多进程 简单, 隔离性好, 进程间几乎无影响 开销最大 多线程 目前使用最多 ...
- golang 并发顺序输出数字
参考 package main import ( "fmt" "sync/atomic" "time" ) func main() { va ...
- Golang并发原理及GPM调度策略(一)
其实从一开始了解到go的goroutine概念就应该想到,其实go应该就是在内核级线程的基础上做了一层逻辑上的虚拟线程(用户级线程)+ 线程调度系统,如此分析以后,goroutine也就不再那么神秘了 ...
- Golang - 并发编程
目录 Golang - 并发编程 1. 并行和并发 2. go语言并发优势 3. goroutine是什么 4. 创建goroutine 5. runtime包 6. channel是什么 7. ch ...
- golang 并发demo 写入 redis
原文链接:golang 并发demo 写入 redis 源代码: package main import ( "fmt" "runtime" "str ...
- 4种Golang并发操作中常见的死锁情形
摘要:什么是死锁,在Go的协程里面死锁通常就是永久阻塞了,你拿着我的东西,要我先给你然后再给我,我拿着你的东西又让你先给我,不然就不给你.我俩都这么想,这事就解决不了了. 本文分享自华为云社区< ...
- <转>golang 并发性能数据
1.管道chan吞吐极限10,000,000,单次Put,Get耗时大约100ns/op,无论是采用单Go程,还是多Go程并发(并发数:100, 10000, 100000),耗时均没有变化,Go内核 ...
- go/wiki/MutexOrChannel Golang并发:选channel还是选锁?
https://mp.weixin.qq.com/s/JcED2qgJEj8LaBckVZBhDA https://github.com/golang/go/wiki/MutexOrChannel M ...
随机推荐
- 微信小程序结合原生JS实现电商模板(一)
前几天遇到一个朋友求助,实现购物车的相关功能,一时心血来潮,想着抽空搭建一个小程序电商平台(虽然网上有很多,但还是自己撸一遍才是王道),所以在工作之余整了一个仓库,今天提交了第一次代码,已经满足了朋友 ...
- boxcox1p归一化+pipeline+StackingCVRegressor
找到最好的那个参数lmbda. from mlxtend.regressor import StackingCVRegressor from sklearn.datasets import load_ ...
- CF678F题解
首先题意中的有撤销操作,直接李超树肯定不行,题目允许离线,所以考虑线段树分治 所以问题就变成了求一次函数最大值 这不是李超树板子吗??? 然后可以对每个节点都建立动态开点李超树,查询的时候直接从叶子节 ...
- jdbc.properties/(驱动、URL、用户名、密码)跟换数据库时改该配置文件/Untitled Text File格式
背景:这几天从阿里云上面购买了云服务器,最垃圾的那种,还送oss和EDS数据库服务器,只不过EDS数据库服务器只有一个月的,就主动升级为一年的,49还是59忘了.对于配置这种EDS过程中,产生的一个念 ...
- 给R语言RStudio添加阿里云镜像源
镜像下载.域名解析.时间同步请点击阿里云开源镜像站 方法一: 打开RStudio,输入options()$repos查看默认镜像源情况 options()$repos 打开tools工具栏,找到Glo ...
- 在线Remix链接本地文件夹
问题 1.本地Remix环境版本滞后于在线编译器,新版本的语法在旧版本编译器中出现错误. 2.没有配置Vscode编译器,不便导入项目. 解决方案 *本解决方案基于Mac系统 创建共享文件夹 在本地创 ...
- 什么是Spring Cloud Bus?
spring cloud bus 将分布式的节点用轻量的消息代理连接起来,它可以用于广播配置文件的更改或者服务直接的通讯,也可用于监控. 如果修改了配置文件,发送一次请求,所有的客户端便会重新读取配置 ...
- 在 Spring中如何注入一个java集合?
Spring提供以下几种集合的配置元素: 类型用于注入一列值,允许有相同的值. 类型用于注入一组值,不允许有相同的值. 类型用于注入一组键值对,键和值都可以为任意类型. 类型用于注入一组键值对,键和值 ...
- SpringMVC的入门程序
1.环境准备(jar包) 2.在web.xml中配置前端控制器 <!-- springmvc 前端控制器 --> <servlet> <servlet-name>s ...
- 学习Haproxy (八)
Unix套接字命令(Unix Socket commands) socat是一个多功能的网络工具,名字来由是"Socket CAT",可以看作是netcat的N倍加强版,socat ...