Go并发编程 | 青训营笔记

57 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

goroutine

goroutine与OS线程的区别

  • OS线程由OS内核来调试,控制成本从一个线程切换到另一个线程需要完整的上下文切换,上下文切换成本较高。
  • Go调试器将使用一个称为m:n的调试技术,将m个goroutine调度到n个OS线程。
  • OS线程调度由硬件时钟触发,而goroutine调试由Go语言结构触发
  • OS线程有一个固定大小的栈内存(通常为2MB),goroutine的栈内存初始很小(通常为2KB),可以按需扩大和缩小。

goroutine的使用

启动一个goroutine

go func()

示例

并发地打印0~9

func print(i int) {
	fmt.Println(i)
}

func main() {
	for i := 0; i < 10; i++ {
		go print(i)
	}
	time.Sleep(time.Second)
}

main函数运行完后,其它的goroutine会被强制结束。因此让main等待1秒再结束。

channel

channel是可以让一个goroutine发送特定特到另一个goroutine的一种机制,实现gorutine之间的通信。

不要通过共享内存来实现通信,而应该通过通信来共享内在。

channel的使用

创建channel

ch := make(chan int)创建一个元素类型是int的channel

发送语句

ch <- x向ch 发送一个元素x,goroutine会阻塞,直到其它goroutine从ch中读取了x.

接收语句

x = <-ch从ch读取一个元素,从过ch中没有元素,goroutine会阻塞,直到其它goroutine向ch发送元素。

关闭channel

close(ch) 关闭后的发送操作会导致宕机。接收操作将获取所有己发送的值,直到通道为空。

示例:

func output(ch chan int) {
	for i := 0; i < 100; i++ {
		ch <- i
		time.Sleep(time.Second)
	}
}

func main() {
	ch := make(chan int)
	go output(ch)
	for {
		x := <-ch
		fmt.Println(x)
	}
}

接收操作返回两个返回值,第一个是接收的元素,第二个是一个存尔值,它表示接收是否成功。如果通道己关闭且所有的元素都已经输出完,第二个返回值就为false

for {
    x, ok := <-ch
    if !ok {
        break //通道关闭且读完,退出循环
    }
}

也可以用range循环接收通道的元素,在接收完最后一个值后会自动退出循环

for x := range ch {   
}

单向channel

chan<- int 是一个只能发送的channel

<-chan int是一个只能接收的channel

func output(ch chan<- int) {
	for i := 0; i < 100; i++ {
		ch <- i
		time.Sleep(time.Second)
	}
}

func print(ch <-chan int) {
	for x := range ch {
		fmt.Println(x)
	}
}

func main() {
	ch := make(chan int)
	go output(ch)
	go print(ch)
	time.Sleep(time.Second * 20)
}

缓冲channel

带缓冲的channel有一个元素队列。向缓冲channel发送元素,不会阻塞goroutine等待接收,而是报元素放入缓冲队列。从channel接收元素是会获得缓冲队列的队首元素。

如果发送元素时缓冲队列已满,会阻塞等待元素出队。

如果读取元素时缓冲队列为空,会阻塞等等发送元素。

竞态

竞态是指多个goroutine在按某些交错顺序执行时程序无法给出正确的结果。

数据竞态发生于两个goroutine并发地读写同一个变量且至少有一个是写入时。

互质锁sync.Mutex

Lock方法用于获取锁,Unlock方法用于释放锁。一个锁只能被一个goroutine。如果一个goroutine获取了锁,且没有释放。其它goroutine调用Lock方法时会阻塞,直到锁被释放它才能获取锁。锁一般用来保护共享变量,防止数据竞态的发生。在Lock和Unlock之间的代码可以自由地读写共享变量,不会被其它goroutine影响。

var (
	x int64
    lock sync.Mutex
)

func addWithLock() {
    lock.Lock()
    x += 1
    lock.Unlock()
}

读写互斥锁sync.RWMutex

RWMutex可以调用RLock和RUlock方法分别来获取和释放一个读锁,读锁可以被多个goroutine同时获取。

可以调用Lock和Unlock方法来获取写锁,写锁只能被一个goroutine获取。

WaitGroup

sync.WaitGroup维护了一个计数器,

Add(delta int)计数器加delta

Done()计数器减1

Wait()阻塞直到计数器归零

func print(i int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Println(i)
}

func main() {
	wg := sync.WaitGroup{}
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go print(i, &wg)
	}
	wg.Wait()
	fmt.Println("Done")
}