Bootstrap

Go语言goroutine线程和channel通信

Go语言的并发编程通过**goroutinechannel来实现。goroutine是一种轻量级线程,而channel则是Go提供的用于goroutine之间通信**的工具。

1. goroutine的使用

**goroutine**是一种并发执行的函数。使用go关键字可以将一个函数或方法运行在一个新的goroutine中,而不阻塞当前的执行。

示例 1:启动一个简单的goroutine
package main

import (
    "fmt"
    "time"
)

func sayHello() {
    fmt.Println("Hello, Go!")
}

func main() {
    go sayHello() // 启动一个新的 goroutine
    time.Sleep(time.Second) // 主 goroutine 暂停 1 秒,以确保 sayHello 有时间运行
    fmt.Println("Main function done")
}

time.Nanosecond:1纳秒
time.Microsecond:1微秒
time.Millisecond:1毫秒
time.Second:1秒
time.Minute:1分钟
time.Hour:1小时

在这个例子中:

  • go sayHello()在一个新的goroutine中运行。
  • main函数暂停1秒钟,以确保sayHello有足够的时间完成。因为goroutine是异步执行的,所以不等待它结束就会继续执行主程序。
示例 2:在主函数中等待多个goroutine
package main

import (
    "fmt"
    "sync"
)

func printNumber(id int, wg *sync.WaitGroup) {
    defer wg.Done() //使用 defer wg.Done() 确保在 goroutine 结束时调用 Done() 方法,减少 WaitGroup 的计数器。
    fmt.Printf("Goroutine %d is running\n", id)
}

func main() {
    var wg sync.WaitGroup // 使用 WaitGroup 等待所有 goroutine 完成

    for i := 1; i <= 5; i++ {
        wg.Add(1) // 每启动一个 goroutine 就增加计数
        go printNumber(i, &wg)
    }

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("All goroutines completed")
}

在这个例子中:

  • sync.WaitGroup用于等待多个goroutine完成。
  • wg.Add(1)在每个goroutine启动时增加计数,defer wg.Done()在每个goroutine结束时减少计数。
  • wg.Wait()阻塞主goroutine,直到所有计数都归零。

2. channel的使用

**channel**是Go中的通信机制,允许多个goroutine间安全地传递数据。channel可以是无缓冲(同步)或有缓冲(异步)的。

示例 3:使用无缓冲的channel

无缓冲的channel要求发送和接收必须同步,即发送和接收操作必须同时准备好,才能完成数据传递。

package main

import (
    "fmt"
)

func sendData(ch chan int) {
    ch <- 10 // 将数据发送到 channel
    fmt.Println("Data sent to channel")
}

func main() {
    ch := make(chan int) // 创建无缓冲 channel

    go sendData(ch) // 启动一个 goroutine 发送数据

    data := <-ch // 从 channel 接收数据
    fmt.Println("Received data:", data)
}

在这个例子中:

  • ch := make(chan int)创建了一个无缓冲的channel
  • sendData函数将数据发送到channel,同时主函数从channel接收数据。这两个操作是同步完成的。
示例 4:使用有缓冲的channel

有缓冲的channel允许在缓冲区满之前发送多个数据,而不需要立即接收。

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 3) // 创建一个缓冲区大小为 3 的 channel

    ch <- 1
    ch <- 2
    ch <- 3 // 可以发送三个数据到 channel 而无需立即接收

    fmt.Println(<-ch) // 从 channel 接收数据:1
    fmt.Println(<-ch) // 从 channel 接收数据:2
    fmt.Println(<-ch) // 从 channel 接收数据:3
}

在这个例子中:

  • make(chan int, 3)创建了一个缓冲区大小为3的channel
  • 可以连续发送三个数据到channel,在缓冲区未满时不需要立即接收。

3. 使用channel同步goroutine

可以使用channel同步多个goroutine的执行,例如在一个goroutine完成任务后通知另一个goroutine继续执行。

示例 5:使用channel同步goroutine
package main

import (
    "fmt"
)

func worker(done chan bool) {
    fmt.Println("Working...")
    done <- true // 完成任务后发送通知
}

func main() {
    done := make(chan bool) // 创建一个无缓冲 channel

    go worker(done) // 启动 worker goroutine

    <-done // 等待 worker goroutine 完成
    fmt.Println("Worker finished")
}

在这个例子中:

  • done是一个无缓冲的channel,用于同步main函数和worker函数。
  • worker完成任务后,会向done发送一个值,main函数会阻塞在<-done直到接收到该值。

4. ✨ select多路复用

select用于同时监听多个channel操作,当多个channel都准备好时,select会随机选择一个执行。

示例 6:select的使用
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "Message from ch1"
    }()

    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "Message from ch2"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

在这个例子中:

  • select语句会等待ch1ch2中的一个准备好,接收到第一个消息后输出结果。
  • 因为ch1会先于ch2发送消息,所以会先打印来自ch1的消息。

5. close关闭channel

关闭channel可以告知接收方不再有数据发送。close关闭channel后不能再次向其中发送数据,否则会引发运行时恐慌。

示例 7:关闭channel
package main

import (
    "fmt"
)

func producer(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch) // 关闭 channel
}

func main() {
    ch := make(chan int)
    go producer(ch)

    for num := range ch { // 迭代接收数据,直到 channel 关闭
        fmt.Println(num)
    }
    fmt.Println("Channel closed")
}

在这个例子中:

  • close(ch)用于关闭channel,通知接收方没有更多数据。
  • for num := range ch会自动接收channel中的数据,直到channel关闭。

6. goroutinechannel和并发编程的最佳实践

  • 避免在多个goroutine中同时写入同一个channel:多个goroutine写入同一个channel时,需要确保并发安全,通常需要借助sync.Mutexsync.WaitGroup来控制。
  • 不要对已关闭的channel写入数据:向已关闭的channel写入数据会导致运行时恐慌,因此确保只关闭一次channel,并且关闭后不再写入。
  • 利用select处理多通道场景select可以实现多路复用,有效处理多个channel的同时监听需求。
  • 考虑使用带缓冲的channel来提升性能:对于一些高吞吐量场景,有缓冲的channel可以减少阻塞,提高效率。

总结

  • goroutine:是Go语言中并发执行的轻量级线程,通过go关键字启动。
  • channel:是goroutine之间进行通信和同步的工具,分为无缓冲和有缓冲两种。
  • select:用于监听多个channel操作,方便处理多路复用。
  • close:关闭channel后可以通知接收方不再有数据发送。
;