三十的博客

Go 协程01-基础用法

本文内容基于 AI 生成结果整理,可能包含不准确信息,仅供参考使用。
发布时间
阅读量 加载中...

前置知识

进程和线程说明

  1. 进程就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
  2. 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位
  3. 一个进程可以创建销毁多个线程,同一个线程中的多个进程可以并发执行
  4. 一个程序至少有一个进程,一个进程至少有一个线程

并发和并行

  1. 多线程程序在单核上运行,就是并发
  2. 多线程程序在多核上运行,就是并行

并发:因为是在一个 cpu 上,比如有 10 个线程,每个线程执行 10 毫秒(进行轮询操作),从人的角度看,好像这 10 个线程都在运行,但是从微观上看,在某一个时间点看,其实只有一个线程在执行,这就是并发。

并行:因为是在多个 cpu 上(比如有 10 个 cpu),比如有 10 个线程,每个线程执行 10 毫秒(各自在不同的 cpu 上执行),从人的角度看,这 10 个线程都在运行,但是从微观上看,在某一个时间点看,也同时有 10 个线程在执行,这就是并行。

协程和主线程

  1. 主线程(有人理解成线程/也可以理解成进程):一个 go 线程上,可以起多个协程,可以理解成协程是轻量级的线程

  2. 协程的特点

Goroutine(协程)

概念:

特点:

go
func say(s string) {
  	time.Sleep(2 * time.Second)
    fmt.Println(s)
}

func main() {
    go say("hello") // 启动 goroutine
    say("world")    // 主 goroutine
}
当主 goroutine 执行完毕时,所有的 goroutine 都会被强制结束。

Channel(通道)

无缓冲 Channel

go
ch := make(chan int) // 无缓冲 channel

特点:

工作原理:

txt
发送者 -> [无缓冲 channel] -> 接收者
(阻塞直到接收)        (阻塞直到发送)

使用场景:

示例:

go
package main

import (
	"fmt"
	"time"
)

func worker(done chan<- bool) {
	fmt.Println("working...")
	time.Sleep(2 * time.Second)
	done <- true
}

func main() {
	done := make(chan bool)
	go worker(done)

	<-done

	fmt.Println("done")
}

带缓冲 Channel

go
ch := make(chan int, 3) // 缓冲大小为3的 channel

特点:

工作原理:

txt
发送者 -> [缓冲 channel(容量N)] -> 接收者
(仅当缓冲区满时阻塞) (仅当缓冲区空时阻塞)

使用场景:

示例:

go
func main() {
    ch := make(chan int, 2) // 缓冲大小为2
    ch <- 1 // 不阻塞
    ch <- 2 // 不阻塞
    // ch <- 3 // 这里会阻塞,因为缓冲区已满
    fmt.Println(<-ch) // 1
    fmt.Println(<-ch) // 2
}

对比总结

特性 无缓冲 Channel 带缓冲 Channel
创建方式 make(chan T) make(chan T, N)
同步性 强同步 弱同步
阻塞时机 发送和接收必须同时准备好 仅在缓冲区满/空时阻塞
数据安全 高(严格同步) 中(可能丢失数据)
性能 较低(频繁阻塞) 较高(减少阻塞)
典型用途 信号通知、严格同步 流量控制、解耦生产消费

Select 多路复用

概念:

特点:

示例:

go
package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		time.Sleep(time.Second * 1)
		c1 <- "one"
	}()

	go func() {
		time.Sleep(time.Second * 2)
		c2 <- "two"
	}()

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

WaitGroup(等待组)

概念:

特点:

示例:

go
package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()

	fmt.Printf("%d worker start \n", id)
	time.Sleep(time.Second)
	fmt.Printf("%d worker end \n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	wg.Wait()
	fmt.Println("all workers completed")
}

Mutex(互斥锁)

概念:

特点:

示例:

go
package main

import (
	"fmt"
	"sync"
	"time"
)

type safeCounter struct {
	mu sync.Mutex
	v  map[string]int
}

func (sc *safeCounter) Inc(key string) {
	sc.mu.Lock()
	sc.v[key]++
	sc.mu.Unlock()
}

func (sc *safeCounter) Val(key string) int {
	sc.mu.Lock()
	defer sc.mu.Unlock()
	return sc.v[key]
}

func main() {
	c := safeCounter{v: make(map[string]int)}

	for i := 0; i < 1000; i++ {
		go c.Inc("lei-cool")
	}

	time.Sleep(time.Second)
	fmt.Println(c.Val("lei-cool"))
}

选择指南

  1. 何时用 channel
  1. 何时用 mutex
  1. 何时用 WaitGroup
#Goroutine #Golang