Goroutine学习笔记(二)

Goroutine与锁

在进行并发编程时,很多时候都需要涉及到变量的共享,下面这段代码创建了2个Goroutine来访问变量a并对a进行自加操作,a预期结果应为200000

goroutine-without-lock.go

package main

import (
	"fmt"
	"sync"
)

func main() {
	a := 0
	var n sync.WaitGroup
	for i := 0; i < 2; i++ {
		n.Add(1)
		go func() {
			defer n.Done()
			for j := 0; j < 100000; j++ {
				a++
			}
		}()
	} //创建2个Goroutine
	n.Wait()
	fmt.Printf("a = %d\n", a)
}

大多数情况下并不能得到正确的结果

假设此时a的值为10000,那么在Goroutine [1]读取a的值后,并且未在内存中写入a加1之后的值的这段时间,如果Goroutine [2]此时访问a的值,它得到的值为10000,而不是10001,这样就造成了Goroutine [2]并没有读取到Goroutine [1]更新后的数值,因此会出现少加的情况

解决该问题的办法就是当Goroutine涉及到有关变量a的执行语句时,要确保读取和写入操作完成后其它Goroutine才能访问变量a

goroutine-with-lock.go

package main

import (
	"fmt"
	"sync"
)

func main() {
	a := 0
	var mu sync.Mutex
	var n sync.WaitGroup
	for i := 0; i < 2; i++ {
		n.Add(1)
		go func() {
			defer n.Done()
			for j := 0; j < 100000; j++ {
				mu.Lock()
				a++
				mu.Unlock()
			}
		}()
	}
	n.Wait()
	fmt.Printf("a = %d\n", a)
}

每当Goroutine执行到涉及变量a的语句时,先申请锁,更新完a的值后在释放锁,当一个Goroutine持有锁时,其它的Goroutine都会等待锁释放后再执行申请锁的操作,这样就保证了每次只有一个Goroutine执行变量a的读取和写入操作

加锁的技巧

为了避免程序运行时不必要的等待,在加锁时需要注意仅当Goroutine需要更改共享变量的值时再获取锁,更改完共享变量的值立刻释放锁

一个不恰当的例子

lock-whole-goroutine-execution_time.go

package main

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

const (
	UNASSIGN  = 0 //未分配
	COMPLETED = 1 //执行完毕
)

func main() {
	task := make([]int, 10)
	var mu sync.Mutex
	var n sync.WaitGroup
	for index := range task {
		task[index] = UNASSIGN
	}
	for index := range task {
		n.Add(1)
		go func(index int) {
			defer n.Done()
			mu.Lock()
			time.Sleep(1 * time.Second)
			task[index] = COMPLETED
			mu.Unlock() //在整个Goroutine加锁
		}(index)
	}
	n.Wait()
	fmt.Printf("All task done!\n")
}

在这个例子中,由于在整个Goroutine执行语句进行加锁,导致整个程序执行了10s,结果和串行执行所需时间一样

如果在Goroutine中等待任务完成后(time.Sleep(1 * time.Second)在此处相当于执行任务,通常情况下任务可以为I/O读写,爬虫请求等等),再申请锁会极大的加快程序执行效率,将上面的代码time.Sleep(1 * time.Second)mu.Lock()互换位置再执行,整个程序仅需1s就可以执行完毕

上述代码其实不适用sync.WaitGroup也可以实现等待操作,主要思想就是通过循环遍历检查所有的任务是否执行完毕,如果所有任务执行完毕退出循环

lock-without-waitgroup.go

package main

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

const (
	UNASSIGN  = 0 //未分配
	COMPLETED = 1 //执行完毕
)

func main() {
	task := make([]int, 10)
	var mu sync.Mutex
	for index := range task {
		task[index] = UNASSIGN
	}
	for index := range task {
		go func(index int) {
			time.Sleep(1 * time.Second)
			mu.Lock()
			task[index] = COMPLETED
			mu.Unlock()
		}(index)
	}

	for {
		taskDone := true
		for index := range task {
			taskDone = taskDone && (task[index] == COMPLETED)
		} //当所有任务执行完毕时,taskDone为true
		if taskDone {
			break
		} //当所有任务执行完毕时,退出循环
	}
	fmt.Printf("All task done!\n")
}

上面的代码还可以优化一下,由于使用的是for {}语句,会一直占用CPU,为了避免其一直占用CPU,在for循环内部可以添加time.Sleep(100 * time.Millisecond)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值