Goroutines和Channels

本文深入探讨Go语言中的并发机制,重点讲解goroutine的使用,包括主goroutine的启动与新goroutine的创建。通过并发的Clock服务示例展示goroutine如何实现多客户端并发服务。接着,详细阐述了channel的概念,包括无缓存与有缓存channel的发送与接收,以及如何利用channel构建并发安全的通信。最后,通过实例展示了如何构建串联的channel形成pipeline,并讨论了单向channel和带缓存channel的应用。
摘要由CSDN通过智能技术生成

goroutine

可以简单类比作一个线程

main goroutine

当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。

创建新goroutine

新的goroutine会用go语句来创建。

f()    // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait

并发的Clock服务

去掉go只能一个客户端接收到时间,加上go多个客户端可以同时接收到时间

package main

import (
	"io"
	"log"
	"net"
	"time"
)

func main() {
	// 创建listener,阻塞监听网络端口的连接
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	// 无限循环来接受连接并handle
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal(err)
			continue
		}
		// go创建goroutine,让每一次handleConn的调用都进入独立的goroutine
		go handleConn(conn)
	}
}

func handleConn(c net.Conn) {
	defer c.Close()
	for {
		_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
		if err != nil {
			return
		}
		// 每隔一秒调用一次
		time.Sleep(1 * time.Second)
	}
}

Channels

一个channel是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有特殊的类型,也就是channels可发送的数据的类型。比如发送int类型数据的channel一般写为chan int

创建

ch := make(chan int) // 无缓存的channel
ch := make(chan int, 3) // 缓存容量为3的channel

发送和接收

一个channel有发送和接受两个主要操作,都是通信行为。发送和接收两个操作都使用<-运算符

ch <- x  // a send statement
x = <-ch // a receive expression in an assignment statement
<-ch     // a receive statement; result is discarded

无缓存的Channels(同步Channels)

同步Channels
 一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。所以也被称为同步Channels

例子
让主goroutine等待后台goroutine完成工作后再退出。主goroutine中的mustCopy函数返回后,调用conn.Close()关闭连接。主goroutine在退出前先等待从done对应的channel接收一个值。因此总是可以在程序退出前正确输出done消息。

package main

import (
	"io"
	"log"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn) // NOTE: ignoring errors
		log.Println("done")
		done <- struct{}{} // signal the main goroutine
	}()
	mustCopy(conn, os.Stdin)
	conn.Close()
	<-done // wait for background goroutine to finish
}

func mustCopy(dst io.Writer, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

串联的Channels(Pipeline)

Channels也可以用于将多个goroutine连接在一起,一个Channel的输出作为下一个Channel的输入。这种串联的Channels就是所谓的管道(pipeline)。
在这里插入图片描述
例子
第一个goroutine是一个计数器,生成1-99,然后通过channel将该整数序列发送给第二个goroutine;第二个goroutine是一个求平方的程序,对收到的每个整数求平方,然后将平方后的结果通过第二个channel发送给第三个goroutine;第三个goroutine是一个打印程序,打印收到的每个整数。

func main() {
    naturals := make(chan int)
    squares := make(chan int)

    // Counter
    go func() {
        for x := 0; x < 100; x++ {
            naturals <- x
        }
        // channels可以不用close,会被垃圾自动回收。但是打开的文件需要手动调用close
        close(naturals)
    }()

    // Squarer
    go func() {
    	// 可以用range循环来依次从channel接收数据,当channel被关闭并且没有值可接收时跳出循环。
        for x := range naturals {
            squares <- x * x
        }
        close(squares)
    }()

    // Printer (in main goroutine)
    for x := range squares {
        fmt.Println(x)
    }
}

单方向的Channel

可以将channel作为函数参数,一般专门用于只发送或者只接收。类型chan<- int表示一个只发送int的channel,类型<-chan int表示一个只接收int的channel,只能接收不能发送。

// 只发送
func counter(out chan<- int) {
    for x := 0; x < 100; x++ {
        out <- x
    }
    close(out)
}
// 传入一个只发送与一个只接收的channel
func squarer(out chan<- int, in <-chan int) {
    for v := range in {
        out <- v * v
    }
    close(out)
}
// 只接收
func printer(in <-chan int) {
    for v := range in {
        fmt.Println(v)
    }
}

func main() {
    naturals := make(chan int)
    squares := make(chan int)
    go counter(naturals)
    go squarer(squares, naturals)
    printer(squares)
}

带缓存的Channels

带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数创建channel时通过第二个参数指定的。

// 创建了一个可以持有三个字符串元素的带缓存Channel
ch = make(chan string, 3)

作用
●初始情况
在这里插入图片描述
●发送三个值后
我们可以在无阻塞的情况下连续向新创建的channel发送三个值:

ch <- "A"
ch <- "B"
ch <- "C"

此刻,channel的内部缓存队列将是满的(图8.3),如果有第四个发送操作将发生阻塞。
在这里插入图片描述
●接收一个值后
如果我们接收一个值,

fmt.Println(<-ch) // "A"

那么channel的缓存队列将不是满的也不是空的(图8.4),因此对该channel执行的发送或接收操作都不会发生阻塞。通过这种方式,channel的缓存队列解耦了接收和发送的goroutine。
在这里插入图片描述
获取缓存容量

// 用内置的cap函数获取
fmt.Println(cap(ch)) // "3"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值