Go 语言多线程

31 阅读2分钟

案例 1:多线程

package main

import "fmt"
import "sync"

var mu sync.Mutex;

func doSum(num *int, wg *sync.WaitGroup) {
	// mutex
	mu.Lock();
	*num++;
	mu.Unlock();
	defer wg.Done()
}

func main() {
	var a int = 0;
	var wg sync.WaitGroup
	for i := 0; i<100; i++ {
		wg.Add(1)
		go doSum(&a, &wg);
	}

	fmt.Println("sum is ", a); // 大概率不是100
	
	wg.Wait()

	fmt.Println("sum is definitely", a) // 100,绝对的
}
  1. 通过 wg.Wait 确保go routine 全部结束,否则会阻塞后面的打印
  2. 通过 mutex 确保 num 变量的读写原子性,虽然是一行代码,如果没有 mutex,num++实际上是对应,读取,加 1,写入3 个操作,线程切换会分割这 3 步,导致结果不为 100

案例 2:通道——无缓冲

  1. 正确的实现
package main

import "fmt"

func main() {
    ch := make(chan int)  // 无缓冲
    
    // 先启动接收者
    go func() {
        fmt.Println("等待数据...")
        data := <-ch
        fmt.Println("收到:", data)
    }()
    
    // 再发送数据
    fmt.Println("发送数据...")
    ch <- 42
    fmt.Println("发送完成")
}
  1. go func() {}() 实际上是启动(准备了一个goroutine),放到了 goroutine队列
  2. chan<- 会阻塞 goroutine,如果这个代码在主 goroutine ,主的停了,逻辑处理器会去队列找一个其它的可以执行的 goroutine,这时候就会启动 func,读取。

vs

  1. 错误的实现
package main

import "fmt"

func main() {
    ch := make(chan int)  // 无缓冲
    
    // 发送数据
    fmt.Println("发送数据...")
    ch <- 42 // 这里阻塞
    fmt.Println("发送完成")
    
    // 启动接收者,没机会启动啊,上面的写入通道直接阻塞了后面的代码执行,goroutine 都没机会进入队列
    go func() {
        fmt.Println("等待数据...")
        data := <-ch
        fmt.Println("收到:", data)
    }()
}

结论:先启动接受者,在执行发送消息,或者先启动发送者 子 goroutine,再读取。即非缓冲 chan,读写都是会阻塞的,必须有另一个提前启动

  1. 通道——缓冲区通道
  • 缓冲channel缓冲区未满时,可以自由读写
  • 只有缓冲区满时发送才阻塞
  • 只有缓冲区空时接收才阻塞