一:简介

因为并发程序要考虑很多的细节,以保证对共享变量的正确访问,使得并发编程在很多情况下变得很复杂。
但是Go语言在开发并发时,是比较简洁的。它通过channel来传递数据。数据竞争这个问题在golang的设计上就进行了规避了。它提倡用通信的方式实现共享,而不要以共享方式来通信
Go语言用2种手段来实现并发程序,goroutine和channel,其支持顺序通信进程(communicating sequential processes),简称为CSP。CSP是一种现代的并发编程模型,在这种编程模型中,值会在不同的运行实例(goroutine)中传递。

二:Goroutine

在Go语言中,每一个并发的执行单元就叫做goroutine。
每个goroutine都对应一个非常简单的模型:它是一个并发的执行函数,并且在多个并发的goroutine间,资源是共享的。goroutine非常轻量,创建的开销很少。

goroutine的用法:
直接在函数前加上一个关键字:go。
go func() {}

例子:

package main

import (
"fmt"
"time"
) func main() {
fmt.Println("In main")
go longSleep()
go shortSleep() fmt.Println("sleep ")
time.Sleep(10 * 1e9)//ns,符号 1e9 表示 1 乘 10 的 9 次方,e=指数
fmt.Println("the end of main")
} func longSleep() {
fmt.Println("longSleep begin")
time.Sleep(5 * 1e9)
fmt.Println("longSleep end")
} func shortSleep() {
fmt.Println("shortSleep begin")
time.Sleep(2 * 1e9)
fmt.Println("shortSleep end")
}

运行结果:

In main
sleep
longSleep begin
shortSleep begin
shortSleep end
longSleep end
the end of main

main() ,longSleep() 和 shortSleep() 这3个函数都是独立的处理单元按顺序启动,然后开始并行运行。为了模拟
运算时间的损耗,我们使用了sleep()函数,这个函数可以按照指定时间来暂停函数或协程执行。

如果我们不在main()函数中sleep()较长的时间,那么main() 函数结束时,其他协程运行的程序也会结束。main()程序退出,它不会等待任何其他非main协程的结束。
协程是独立的处理单元,一旦陆续启动一些协程,就无法确定他们是什么时候正在开始运行的。

三:通道channel

上面我们讲到,协程都是独立运行的,他们之间没有通信。
协程可以使用共享变量来通信,但是不建议这么做。在Go中有一种特殊的类型channle通道,可以通过它来进行goroutine之间的通信,可以避免共享内存的坑。channel的通信保证了同步性。
数据通过通道,同一时间只有一个协程可以访问数据,所以不会出现数据竞争,设计时就是这样的。

3.1 channel语法

channel也是通过make进行分配的,其返回的是指向底层相关数据结构的引用。

  • 1、基础语法
var chan1 chan string
chan1 = make(chan string)
//or
chan1 := make(chan string) //int
intchan := make(chan int) //函数也可以
funcchan := chan func()
  • 2、不带缓冲的channel
var chan2 chan string

chan2 := make(chan string)

chan3 := make(chan string, 0)
  • 3、带缓冲区的channel
//在make第二个参数加上数字,就变成一个带缓冲的channel,
//也是一个双向channel,既可以读也可以写
chan3 := make(chan string, 4)
  • 4、单向channel
//只发送的channel,在类型后面加上一个箭头 <-,只能向channel写数据
var chan4 chan <-int chan4 := make(chan <-int)
//只接收的channel,箭头放在chan前面,只能从channel读取数据
var chan4 <-chan int chan4 := make(<-chan int) //初始化

3.2 channel特性

基础特性

操作 值为 nil 的 channel 被关闭的 channel 正常的 channel
close panic panic 成功关闭
c<- 永远阻塞 panic 阻塞或成功发送
<-c 永远阻塞 永远不阻塞 阻塞或成功接收

happens-before 特性

  1. 无缓冲时,接收 happens-before 发送
  2. 任何情况下,发送 happens-before 接收
  3. close happens-before 接收

3.3 channel用法

3.3.1、无缓冲区

channel无缓冲区,发送方和接收方需要一一配对,不然发送方会一直阻塞,直到数据被接收方取出。
其实无缓冲区channel不管是存消息还是取消息,都会挂起当前goroutine,除非另外一端已经准备好。
无缓冲区的channel永远不会存数据,只负责数据的流通。

  • 从无缓冲channel取数据,必须要有数据流进来才可以,否则当前协程阻塞
  • 数据流入无缓冲channel, 如果没有其他goroutine来拿走这个数据,那么当前协程阻塞

注意:
同步的channel不能只在一个协程中发送和接收,因为会被永远阻塞,数据不能到接收方那里。

package main

import "fmt"

func main() {
chan1 := make(chan int) go func() {
for d := range chan1 {
fmt.Println(d)
}
}() chan1 <- 1 //发送要放在接收协程跑起来后面,因为发送后会阻塞等待接收
chan1 <- 2
chan1 <- 3 close(chan1)
}
import "fmt"

func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}

3.3.2、有缓冲区

有缓冲区
channel创建一个缓冲区,如果缓冲区已满,发送方的主进程或者协程会被阻塞,发送方只能在接收方取走数据后才能从阻塞状态恢复;如果未满就不会阻塞;如果为空,接收方的协程会被阻塞。
上面的这种特性,比如可以控制主进程的退出,因为有时我们碰到主协程退出了,其他的子协程还没有运行完成。

package main

import (
"fmt"
) //-------------
var ichan = make(chan int, 3)
var str string func f() {
str = "hello world"
ichan <- 0
} func main() {
go f()
<-ichan //这里有值,下面才会运行 fmt.Println(str)
}
package main

import (
"fmt"
) func main() {
chan1 := make(chan int, 3)
quit := make(chan bool) //阻塞主进程,防止未处理完的子协程 go func() {
for d := range chan1 { //如果data的缓冲区为空,这个协程会一直阻塞,除非被channel被close
fmt.Println(d)
}
quit <- true
}() chan1 <- 1
chan1 <- 2
chan1 <- 3
chan1 <- 4
chan1 <- 5
close(chan1) //用完需要关闭,否则goroutine会被死锁,因为上面用range,它是不等到信道关闭是不会结束读取的
<-quit //解除阻塞
}

3.3.3、 for...range

上面有的例子是一个一个的取数据,其实golang还提供了for range 来读取channel中的数据。

package main

import (
"fmt"
"time"
) func main() { go func() {
time.Sleep(1 * time.Hour)
}() c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)//如果把close(c)注释掉,程序会一直阻塞在for …… range那一行
}() for i := range c {
fmt.Println(i)
} fmt.Println("end!")
} //range c 产生的迭代值为channel中发送的值,它会一直迭代直到channel被关闭。
//注意:上面的例子中如果把close(c)注释掉,程序会一直阻塞在for …… range那一行

3.3.4、select监听channel

select监测各个channel的数据。
如果有多个channel接收数据,select会随机选择一个case来处理。
你还可以给select加上一个default语句,如果没有case需要处理,那么就会选择default语句。
多个case情况下,如果没有default也没有case需要处理的,那么select会阻塞,只到某个case需要处理。
注意:nil channel 的操作会一直被阻塞,如果没有default的话,select会一直被阻塞。

package main

import (
"fmt"
) func foo(i int) chan int {
c := make(chan int)
go func() {
c <- i
}()
return c
} func main() {
c1, c2, c3 := foo(1), foo(2), foo(3) ichan := make(chan int)
//开一个goroutine监听各个channel数据输出并收集数据到channel
go func() {
for {//for语句循环处理select, 如果只有一个select,那么它只会选一个case处理就结束了
select { //监听c1,c2,c3流出,并全部流入到ichan
case v1 := <-c1:
ichan <- v1
case v2 := <-c2:
ichan <- v2
case v3 := <-c3:
ichan <- v3
}
}
}() //阻塞主协程,取出ichan的数据
for i := 0; i < 3; i++ {
fmt.Println(<-ichan) // 从打印来看我们的数据输出并不是严格的1,2,3顺序
} fmt.Println("end!")
}

输出结果:

2
1
3
end!

3.3.5、超时处理

select还有一个应用超时处理的功能。上面说到如果没有case需要处理,那么select会一直阻塞,这时候我们就可以在一个case下定义一个超时情况,其他case没有数据处理时,到时间点了这个超时case就会处理了,就不会一直阻塞。
我们用time.After,它返回一个类型为 <-chan time 的单向channel,在指定时间发送一个当前时间给channel

package main

import (
"fmt"
"time"
) func main() {
chan1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 3)
chan1 <- "res1"
}() select {
case res := <-chan1: //3秒之后才会有数据进入槽chan1
fmt.Println(res)
case <-time.After(time.Second * 1)://定义超时情况,1秒后超时.这个超时时间比上面的case短,所以先运行这个case
fmt.Println("timeout 1")
}
}

输出:
timeout 1

3.4 close channel

上面的特性我们列举了close channel的情况。

  • channel已经被关闭

close()掉了,你继续往里面写数据,会出现panic。
但是,从这个关闭的channel可以读出已发送的数据,还可以不断的读取零值。
如果是通过range读取数据,channel关闭后for循环会跳出。

通过i, ok := <-c 可以查看channel的状态,判断是零值还是正常读取的值。

c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false

参考

https://colobu.com/2016/04/14/Golang-Channels/

Golang的goroutine协程和channel通道的更多相关文章

  1. Golang 入门 : goroutine(协程)

    在操作系统中,执行体是个抽象的概念.与之对应的实体有进程.线程以及协程(coroutine).协程也叫轻量级的线程,与传统的进程和线程相比,协程的最大特点是 "轻"!可以轻松创建上 ...

  2. golang中goroutine协程调度器设计策略

    goroutine与线程 /* goroutine与线程1. 可增长的栈os线程一般都有固定的栈内存,通常为2MB,一个goroutine的在其声明周期开始时只有很小的栈(2KB),goroutine ...

  3. GO语言的进阶之路-协程和Channel

    GO语言的进阶之路-协程和Channel 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 看过我之前几篇博客小伙伴可能对Golang语言的语法上了解的差不多了,但是,如果想要你的代码 ...

  4. golang中最大协程数的限制(线程)

    golang中最大协程数的限制 golang中有最大协程数的限制吗?如果有的话,是通过什么参数控制呢?还是通过每个协程占用的资源计算? 通过channel控制协程数的就忽略吧. 以我的理解,计算机资源 ...

  5. go语言之进阶篇创建goroutine协程

    1.goroutine是什么 goroutine是Go并行设计的核心.goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现 ...

  6. Golang教程:goroutine协程

    在上一篇中,我们讨论了并发,以及并发和并行的区别.在这篇教程中我们将讨论在Go中如何通过Go协程实现并发. 什么是协程 Go协程(Goroutine)是与其他函数或方法同时运行的函数或方法.可以认为G ...

  7. go 学习 (五):goroutine 协程

    一.goroutine 基础 定义 使用者分配足够多的任务,系统能自动帮助使用者把任务分配到 CPU 上,让这些任务尽量并发运作,此机制在Go中称作 goroutine goroutine 是 Go语 ...

  8. go语言之行--golang核武器goroutine调度原理、channel详解

    一.goroutine简介 goroutine是go语言中最为NB的设计,也是其魅力所在,goroutine的本质是协程,是实现并行计算的核心.goroutine使用方式非常的简单,只需使用go关键字 ...

  9. golang的多协程实践

    go语言以优异的并发特性而闻名,刚好手上有个小项目比较适合. 项目背景: 公司播控平台的数据存储包括MySQL和ElasticSearch(ES)两个部分,编辑.运营的数据首先保存在MySQL中,为了 ...

随机推荐

  1. 利用Post方法进行数据提交

    import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import ...

  2. Elasticsearch:运用scroll接口对大量数据实现更好的分页

    在Elasticsearch中,我们可以通过size和from来对我们的结果来进行分页.但是对于数据量很大的索引,这是有效的吗?Scroll API可用于从单个搜索请求中检索大量结果(甚至所有结果), ...

  3. [USACO19FEB]Cow Dating——找规律

    原题戳这里 题解 显然原题等价于让我们求这个式子\(\prod\limits_{i=l}^{r}(1-p_i)\sum\limits_{i=l}^{r}\frac{p_i}{1-p_i}\)的最大值是 ...

  4. docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /tmp/tfserving/

    注意要是当前的完整路径 pwd查看到完整路径,再加入到source里面即可

  5. CCPC 2017 哈尔滨 L. Color a Tree && HDU 6241(二分+树形DP)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6241 题意:给你一棵有 n 个结点的树,每个结点初始颜色都为白色,有 A 个条件:结点 x_i 的黑色 ...

  6. 09 深科技相关表结构 (未完成)、git

    1.深科技相关 1. 深科技表结构(6表) 深科技4张+2张用户表 - 深科技 用户表 用户Token 文章来源 文章表 通用评论表 通用收藏表 # ######################## ...

  7. 【leetcode】1269. Number of Ways to Stay in the Same Place After Some Steps

    题目如下: You have a pointer at index 0 in an array of size arrLen. At each step, you can move 1 positio ...

  8. not(expr|ele|fn)从匹配元素的集合中删除与指定表达式匹配的元素

    not(expr|ele|fn) 概述 从匹配元素的集合中删除与指定表达式匹配的元素   参数 exprStringV1.0 一个选择器字符串.深圳dd马达 elementDOMElementV1.0 ...

  9. g++版本低于4.7使用C++11

    编译时需要添加: 需要添加头文件#include<memory> g++ -std=gnu++0x share_ptr.cpp -o s 原文: C++11 features are av ...

  10. springboot 生产环境与开发环境配置

    通过修改yml文件里的active属性,prod(生产环境) 与 dev (开发环境)