goroutine

在go语言中,每一个并发的执行单元叫做一个goroutine

这里说到并发,所以先解释一下并发和并行的概念:

并发:逻辑上具备同时处理多个任务的能力

并行:物理上在同一时刻执行多个并发任务

当一个程序启动时,其主函数即在一个单独的goroutine中运行,一般这个goroutine是主goroutine

如果想要创建新的goroutine,只需要再执行普通函数或者方法的的前面加上关键字go

通过下面一个例子演示并发的效果,主goroutine会计算第45个斐波那契函数,在计算的同时会循环打印:-\|/

这里需要知道:当主goroutine结束之后,所有的goroutine都会被打断,程序就会退出

package main

import (
"time"
"fmt"
) func spinner(delay time.Duration){
for {
for _,r := range `-\|/`{
fmt.Printf("\r%c",r)
time.Sleep(delay)
}
}
} func fib(x int) int{
// 斐波那契函数
if x < 2{
return x
}
return fib(x-1) + fib(x-2)
} func main() {
go spinner(100*time.Millisecond) //开启一个goroutine
const n = 45
fibN:= fib(n)
fmt.Printf("\rFib(%d) = %d\n",n,fibN)
}

当第一次看到go的并发,感觉真是太好用了!!!!

所以在网络编程里,服务端都是需要同时可以处理很多个连接,我们看一下下面的服务端和客户端例子

服务端:

package main

import (
"net"
"io"
"time"
"log"
) func handleConn(c net.Conn){
defer c.Close()
for{
_,err := io.WriteString(c,time.Now().Format("15:04:05\r\n"))
if err != nil{
return
}
time.Sleep(1*time.Second)
}
} func main() {
// 监听本地tcp的8000端口
listener,err := net.Listen("tcp","localhost:8000")
if err != nil{
log.Fatal(err)
}
for {
conn,err := listener.Accept()
if err!= nil{
log.Print(err)
continue
}
go handleConn(conn)
}
}

客户端:

package main

import (
"io"
"log"
"net"
"os"
) func mustCopy(dst io.Writer,src io.Reader){
// 从连接中读取内容,并写到标准输出
if _,err := io.Copy(dst,src);err !=nil{
log.Fatal(err)
} } func main(){
conn,err := net.Dial("tcp","localhost:8000")
if err != nil{
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
}

Channel

channel是不同的goroutine之间的通信机制。

一个goroutine通过channel给另外一个goroutine发送信息。

每个channel 都有一个特殊的类型,也就是channel可以发送的数据的类型

我们可以通过make创建一个channel如:

ch := make(chan int)  这就是创建了一个类型为int的channel

默认我们这样创建的是无缓存的channel,当然我们可以通过第二个参数来设置容量

ch := make(chan int,10)

注意:channel是引用类型,channel的零值也是nil

两个相同类型的channel可以使用==运算符比较。如果两个channel引用的是相通的对象,那么比较的结
果为真。一个channel也可以和nil进行比较。

因为channel是在不同的goroutine之间进行通信的,所以channel这里有两种操作:存数据和取数据,而这里两种操作的

方法都是通过运算符:<-

ch <- x  这里是发送一个值x到channel中

x = <- ch 这里是从channel获取一个值存到变量x

<-ch 这里是从channel中取出数据,但是不使用结果

close(ch) 用于关闭channel

当我们关闭channel后,再次发送就会导致panic异常,但是如果之前发送过数据,我们在关闭channel之后依然可以执行接收操作

如果没有数据的话,会产生一个零值

基于channel发送消息有两个重要方面,首先每个消息都有一个值,但是有时候通讯的事件和发送的时刻也同样重要。

我们更希望强调通讯发送的时刻时,我们将它称为消息事件。有些消息并不携带额外的信息,它仅仅是用做两个goroutine之间的同步,这个时候我们可以用struct{}空结构体作为channel元素的类型

 无缓存的channel

基于无缓存的channel的发送和接受操作将导致两个goroutine做一次同步操作,所以无缓存channel有时候也被称为同步channel

串联的channel (Pipeline)

channel也可以用于多个goroutine连接在一起,一个channel的输出作为下一个channel的输入,这种串联的channel就是所谓的pipeline

通过下面例子理解,第一个goroutine是一个计算器,用于生成0,1,2...形式的整数序列,然后通过channel将该整数序列

发送给第二个goroutine;第二个goroutine是一个求平方的程序,对收到的每个整数求平方,然后将平方后的结果通过第二个channel发送给第三个goroutine

第三个goroutine是一个打印程序,打印收到的每个整数

package main

import (
"fmt"
) func main(){
naturals := make(chan int)
squares := make(chan int) go func(){
for x:=0;;x++{
naturals <- x
}
}() go func(){
for {
x := <- naturals
squares <- x * x
}
}() for{
fmt.Println(<-squares)
}
}

但是如果我把第一个生成数的写成一个有范围的循环,这个时候程序其实会报错的

所以就需要想办法让发送知道没有可以发给channel的数据了,也让接受者知道没有可以接受的数据了

这个时候就需要用到close(chan)

当一个channel被关闭后,再向该channel发送数据就会导致panic异常

当从一个已经关闭的channel中接受数据,在接收完之前发送的数据后,并不会阻塞,而会立刻返回零值,所以在从channel里接受数据的时候可以多获取一个值如:

go func(){
for {
x ,ok := <-naturals
if !ok{
break
}
squares <- x*x
}
close(squares)
}() 第二位ok是一个布尔值,true表示成功从channel接受到值,false表示channel已经被关闭并且里面没有值可以接收

单方向的channel

当一个channel座位一个函数的参数时,它一般总是被专门用于只发送或者只接收

chan <- int :表示一个只发送int的channel,只能发送不能接收

< chan int : 表示一个只接受int的channel,只能接收不能发送

当然在有时候我们需要获取channel内部缓存的容量,可以通过内置的cap函数获取

而len函数则返回的是channel内实际有效的元素个数

基于select的多路复用

这里先说一个拥有的知识点:time.Tick函数

这个函数返回一个channel,通过下面代码进行理解:

package main

import (
"time"
"fmt"
) func main() {
tick := time.Tick(1*time.Second)
for countdown :=10;countdown>0;countdown--{
j :=<- tick
fmt.Println(j)
}
}

程序会循环打印一个时间戳

select 语句:

select {
case <-ch1:
// ...
case x := <-ch2:
// ...use x...
case ch3 <- y:
// ...
default:
// ...
}

select语句的形式其实和switch语句有点类似,这里每个case代表一个通信操作

在某个channel上发送或者接收,并且会包含一些语句组成的一个语句块 。

select中的default来设置当 其它的操作都不能够马上被处理时程序需要执行哪些逻辑

channel 的零值是nil,  并且对nil的channel 发送或者接收操作都会永远阻塞,在select语句中操作nil的channel永远都不会被select到。

这可以让我们用nil来激活或者禁用case,来达成处理其他输出或者输出时间超时和取消的逻辑

补充

不同的goroutine之间如何通信

首先我们能够想到的有:全局变量的方式,我们先通过这种本方法来演示:

package main

import (
"time"
"fmt"
) var exits [3]bool func calc(index int){
for i:=0;i<1000;i++{
time.Sleep(time.Millisecond)
}
exits[index] = true
} func main() {
start := time.Now().UnixNano()
go calc(0)
go calc(1)
go calc(2) for{
if exits[0] && exits[1] &&exits[2]{
break
}
}
end := time.Now().UnixNano()
fmt.Println("finished,const:%d ms",end-start)
}

这种方法其实比较笨,go为我们提供了锁同步的方式 sync.WaitGroup,演示代码为:

//等待一组goroutine执行完成

package main

import (
"time"
"fmt"
"sync"
) var waitGroup sync.WaitGroup func calc(index int){
for i:=0;i<1000;i++{
time.Sleep(time.Millisecond)
}
//执行完成的时候Done
waitGroup.Done()
} func main() {
start := time.Now().UnixNano()
for i:=0;i<3;i++{
// 每次在调用之前add
waitGroup.Add(1)
go calc(i)
}
//在循环外等待wait
waitGroup.Wait()
end := time.Now().UnixNano()
fmt.Println("finished,const:%d ms",end-start)
}

关于Channel的补充

channel 概念:

类似unix中的管道pipe

先进先出

线程安全,多个goroutine同时访问,不需要加锁

channel是有类型的,一个整数的channel只能存放整数

定时器的补充

//定时器
package main import (
"time"
"fmt"
) func main() {
t := time.NewTicker(time.Second)
for v:= range t.C{
fmt.Println("hello",v)
}
}
// 一次性定时器
package main import (
"time"
"fmt"
) func main() {
select{
case <- time.After(time.Second):
fmt.Println("after")
}
}

超时控制

package main

import (
"time"
"fmt"
) func queryDb(ch chan int){
time.Sleep(time.Second)
ch <- 100
} func main() {
ch := make(chan int)
go queryDb(ch)
t := time.NewTicker(time.Second*4)
select{
case v:=<-ch:
fmt.Println("result:",v)
case <-t.C:
fmt.Println("timeout")

关于单元测试和异常捕获的补充

package main

import (
"time"
"fmt"
) func calc(){
// defer 定义的后面出现错误的都可以捕获到
defer func() {
err := recover()
if err!=nil{
fmt.Println(err)
}
}()
var p *int
*p = 100 }

关于单元测试的简单例子演示:

package test

func add(a,b int)int{
return a+b
}

上面的代码,如果想要做单元测试,我们需要定义一个测试文件,我把上面代码的文件名是calc.go,单元测试代码的文件名calc_test.go

package test

import "testing"

func TestAdd(t *testing.T){
result := add(2,3)
if result != 5{
t.Fatalf("add is not right")
return
}
t.Logf("add is right")
}

这样当我们需要测试add函数的时候只需要在该包下执行go test,效果如下:

如果想要看更详细的可以通过go test -v,效果如下:

Go基础--goroutine和channel的更多相关文章

  1. Go基础系列:channel入门

    Go channel系列: channel入门 为select设置超时时间 nil channel用法示例 双层channel用法示例 指定goroutine的执行顺序 channel基础 chann ...

  2. Go part 8 并发编程,goroutine, channel

    并发 并发是指的多任务,并发编程含义比较广泛,包含多线程.多进程及分布式程序,这里记录的并发是属于多线程编程 Go 从语言层面上支持了并发的特性,通过 goroutine 来完成,goroutine ...

  3. TODO:Go语言goroutine和channel使用

    TODO:Go语言goroutine和channel使用 goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个 ...

  4. goroutine 加 channel 代替递归调用,突破递归调用的层级限制

    package main import ( "fmt" "github.com/davecgh/go-spew/spew" "github.com/B ...

  5. goroutine和channel

    近期在学习golang的goroutine和channel时候有一些疑惑: 带缓冲的channel和不带缓冲的channel有什么区别? goroutine和主进程的有哪些影响和关系? 多个gorou ...

  6. [转帖]go 的goroutine 以及 channel 的简介.

    进程,线程的概念在操作系统的书上已经有详细的介绍.进程是内存资源管理和cpu调度的执行单元.为了有效利用多核处理器的优势,将进程进一步细分,允许一个进程里存在多个线程,这多个线程还是共享同一片内存空间 ...

  7. Go开发[八]goroutine和channel

    进程和线程 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位. 一个进程可以创 ...

  8. Go--关于 goroutine、channel

    Go--关于 goroutine.channel goroutine 协程是一种轻量化的线程,由Go编译器进行优化. Go协程具有以下特点: 有独立的栈空间 共享程序堆中的空间 调度由用户控制 如果主 ...

  9. go并发之goroutine和channel,并发控制入门篇

    并发的概念及其重要性 这段是简单科普,大佬可以跳过 并发:并发程序指同时进行多个任务的程序.在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行 ...

随机推荐

  1. console报错类型

    常见 console报错 Error 错误 EvalError 全局错误 RangeError 引用(范围)错误 ReferenceError 参数(参考)错误 SyntaxError 语法错误 ty ...

  2. 【整理】REACT一些自己感觉需要记的东西

    REACT生命周期: 组件的生命周期可分成三个状态: Mounting:已插入真实 DOM Updating:正在被重新渲染 Unmounting:已移出真实 DOM 生命周期的方法有: compon ...

  3. Java多线程同步问题:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.一个简单的Demo引发的血案 关于线程同步问题我们从一个 ...

  4. 用Vue中遇到的问题和处理方法

    用Vue开发项目有一段时间,在实际项目中遇到一些问题,在里把问题记录下来,并附上解决方案,给遇到同样的问题的码友提供一个解决思路吧: 测试部抛出问题一:在Vue1.0路由vue-router中,当点击 ...

  5. PHP面向对象编程基本原则

    首先祝大家节日快乐!!! 额,不知道你们剁手没,小梦是没有!整整已经错过了第九个年头! 小伙伴是不是有一种感觉,PHP入门的时候简直爱不释手,总是把 "PHP是世界上最好的语言" ...

  6. SSH Secure Shell Client最新版,解决Win10不兼容问题

    SSH的工具很多,像XShell,SecureCRT等等. 不过我一直用的是:SSH Secure Shell Client 主要的原因就是: 软件本身带文件浏览的功能,可以通过拖拽去实现文件上传和下 ...

  7. HDU 1166 敌兵布阵(线段树单点更新,板子题)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  8. [bzoj1969] [Ahoi2005]LANE 航线规划

    tarjan.并查集.树状数组.树链剖分. 时间倒流,变删边为加边. 先求一波边双联通分量,缩点. 题目保证最后还是整张图联通的..所以就是一棵树. 现在的操作就是,将路径上的边权置0(加边时),查询 ...

  9. [HDU3247]Resource Archiver

    AC自动机+状压DP 首先对所有串建AC自动机,然后对于每个资源串,算出从串末走到其他资源串末所需的距离(中途避开非法点) 也就是算出两两间的距离...然后就变成旅行商问题了. 计算距离的时候要考虑一 ...

  10. [bzoj1700]: [Usaco2007 Jan]Problem Solving 解题

    不能贪心!不能贪心!不能贪心! 反正有反例(有的题目月初支付款很少,月末支付款很大,和前面的题凑到一个月的话可能导致下个月写不了= =这时放后一个月,和后面的题一起开始写可能更优) 比如: 50 44 ...