Go基础--goroutine和channel

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,来达成处理其他输出或者输出时间超时和取消的逻辑

原文地址:https://www.cnblogs.com/zhaof/p/8393091.html

时间: 2024-08-29 20:35:47

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

Go基础系列:channel入门

channel基础 channel用于goroutines之间的通信,让它们之间可以进行数据交换.像管道一样,一个goroutine_A向channel_A中放数据,另一个goroutine_B从channel_A取数据. channel是指针类型的数据类型,通过make来分配内存.例如: ch := make(chan int) 这表示创建一个channel,这个channel中只能保存int类型的数据.也就是说一端只能向此channel中放进int类型的值,另一端只能从此channel中读出

Go语言入门(七)goroutine和channel

goroutine和channel goroutine 多线程 func hello() { //fmt.Printf("Hello Goroutine!!\n") for i:=0;i<100;i++ { fmt.Printf("hello:%d\n",i) time.Sleep(time.Millisecond) } } func main() { go hello() //启动了一个独立的线程,使其与下面的代码交替执行,使之成为一个多线程 //fmt.P

go语言系列-从Goroutine到Channel

Golang语言的核心特色 目录 Goroutine 基本介绍 进程和线程介绍 程序.进程和线程的关系示意图 并发和并行 Go协程和Go主线程 快速入门 案例说明 小结 goroutine的调度模型 MPG模式运行的状态 -1 MPG模式运行的状态 - 2 设置Go运行的CPU数 Channel(管道) 看个需求 不同goroutine之间如何通讯 使用全局变量加锁同步改进程序 为什么需要channel channel的基本介绍 定义/声明channel 管道的初始化.写入数据到管道.从管道读取

TODO:Go语言goroutine和channel使用

goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个单词作为关键词,也是与普通函数的区别了.在函数前面加go关键字就可以创建一个新的goroutine进行并发执行. go hello() channel是Go语言提供的goroutine间的通信方式,我们可以使用channel在两个或多个goroutine之家传递消息.channel使用的关键字是用"chan",声明一个传递类型为int的chann

golang goroutine、channel和select

goroutine package main import "fmt" import "time" func printn(id int){ for i := 0;i<10;i++ { fmt.Println(id,":",i) } } func main(){ for i :=0;i<5;i++ { go printn(i) } fmt.Println("waiting...") time.Sleep(time.

golang的goroutine与channel

Golang的goroutine是非抢占式的, 令人相当蛋疼! 有痛不能呻吟...只能配合channel在各goroutine之间传递信号来实现抢占式, 而这形成了golang最灵活与最具性能的核心. 相信, 彩虹总在风雨后... 学会process, thread, routine之间的配合与取舍. 关于channel的猜测: Golang中Channel的阻塞规则, 注意顺序: Write端依赖于Read端, 对于无缓冲Channel, 如果没有goroutine去消费Channel, 则w

golang语言并发与并行——goroutine和channel的详细理解(一)

如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就支持了并发,这与其他语言大不一样,不像以前我们要用Thread库 来新建线程,还要用线程安全的队列库来共享数据. 以下是我入门的学习笔记. Go语言的goroutines.信道和死锁 goroutine Go语言中有个概念叫做goroutine, 这类似我们熟知的线程,但是更轻. 以下的程序,我们串行地去执行两次loop函数: func loop() { for i := 0; i < 10; i++ { f

golang之goroutine和channel

多线程程序在单核上运行,就是并发 多线程程序在多核上运行,不是并行 Go协程和Go主线程 Go主线程(线程):一个Go线程上,可以起多个协程 ,你可以这样理解,协程是轻量级的线程 Go协程的特点: 1)有独立的栈空间 2)共享程序堆空间 3) 调度由用户控制 4)协程是轻量级的线程3 goroutine快速入门 func test() { for i := 1; i <= 10; i++ { fmt.Println("test() hello, world " + strconv

golang goroutine channel [fmt.Println=&gt;String]

初识golang,这部分也不是很了解,百度了一下,做个小记录 goroutine是golang用来做并发处理的,使用起来很简单  go func(){...}(),就是看起来随便用一般而容易go的滥用,所以使用时要仔细斟酌才好. channel 大约是用来在线程间传递数据的,主线程开通一个channel,goroutine往channel中存入内容 1.channel只能用make创建 c := make(chan int) 2.channel中存入数据 c<-2  //把2存入到channel